一、基础知识
在一个长度为n的数组中查找一个数字,如果逐一扫描数组中的每个数字,那么需要O(n)的时间。如果数组是排序的(通常按照递增的顺序排序),那么可以采用二分查找算法进行优化。可以取出位于数组中间的数字并和目标数字比较。如果中间数字正好等于目标数字,那么就找到了目标数字。如果中间数字大于目标数字,那么只需要查找数组的前半部分,这是因为数组是排序的,后半部分的数字都大于或等于中间数字,所以一定都大于目标数字,也就没有必要再在后半部分查找。如果中间数字小于目标数字,那么接下来只需要查找数组的后半部分,这是因为排序数组的前半部分的数字都小于或等于中间数字,所以一定都小于目标数字,也就没有必要再在前半部分查找。
二分查找算法每次将查找范围减少一半,因此对于一个长度为n的数组可能需要O(logn)次查找,每次查找只需要比较当前查找范围的中间数字和目标数字,在O(1)的时间可以完成,因此二分查找算法的时间复杂度是O(logn)。
二、常见算法
1. 山峰数组的顶部
题目:在一个长度大于或等于3的数组中,任意相邻的两个数字都不相等。该数组的前若干数字是递增的,之后的数字是递减的,因此它的值看起来像一座山峰。请找出山峰顶部,即数组中最大值的位置。例如,在数组[1,3,5,4,2]中,最大值是5,输出它在数组中的下标2。
解题思路:
- 首先想找到数组的峰值,峰值必然大于左右两个数,而且左边的数组是递增的,右边的数组是递减的。
- 用二分查找法,来每次看中间值落在递增数组中,还是在递减数组中。如果在递增数组中,那么峰值就在右边;如果在递减数组中,那么峰值就在左边。然后递归查询。
golang代码:
func peakIndexInMountainArray(arr []int) int {
return callPeak(arr, 0)
}
func callPeak(arr []int, tag int) int {
var (
left, right, middle int
)
middle = len(arr)/2
if middle - 1 >= 0 {
left = arr[middle-1]
}
if middle + 1 < len(arr) {
right = arr[middle+1]
}
if arr[middle] > left && arr[middle] > right {
return middle+tag
}
if arr[middle] > left && arr[middle] < right {
return callPeak(arr[middle:], tag+middle)
}
if arr[middle] < left && arr[middle] > right {
return callPeak(arr[:middle+1], tag)
}
return 0
}
2. 排序数组中只出现一次的数字
题目:在一个排序的数组中,除一个数字只出现一次之外,其他数字都出现了两次,请找出这个唯一只出现一次的数字。例如,在数组[1,1,2,2,3,4,4,5,5]中,数字3只出现了一次。
解题思路:
- 二分查找
golang代码:
func singleNonDuplicate(nums []int) int {
if len(nums) == 1 {
return nums[0]
}
if len(nums) == 3 {
if nums[0] == nums[1] {
return nums[2]
} else {
return nums[0]
}
}
middle := len(nums)/2
if nums[middle] != nums[middle-1] && nums[middle] != nums[middle+1] {
return nums[middle]
}
if nums[middle] == nums[middle-1] {
if middle%2 == 1 {
return singleNonDuplicate(nums[middle-1:])
} else {
return singleNonDuplicate(nums[:middle+1])
}
}
if nums[middle] == nums[middle+1] {
if middle%2 == 1 {
return singleNonDuplicate(nums[:middle+2])
} else {
return singleNonDuplicate(nums[middle:])
}
}
return 0
}
3. 吃香蕉
题目:狒狒很喜欢吃香蕉。一天它发现了n堆香蕉,第i堆有piles[i]根香蕉。门卫刚好走开,H小时后才会回来。狒狒吃香蕉喜欢细嚼慢咽,但又想在门卫回来之前吃完所有的香蕉。请问狒狒每小时至少吃多少根香蕉?如果狒狒决定每小时吃k根香蕉,而它在吃的某一堆剩余的香蕉的数目少于k,那么它只会将这一堆的香蕉吃完,下一个小时才会开始吃另一堆的香蕉。
解题思路:
- 碰到单调递增或递减的,就可以使用二分法来解题
golang代码:
func minEatingSpeed(piles []int, h int) int {
max := getMax(piles...)
min := 1
var middle = (max+min)/2
var count int
for max > min {
count = getHour(piles, middle)
if count > h {
min = middle
middle = (max+min)/2
if min+1 == max {
//middle = max
return max
}
} else if count == h {
max = middle
middle--
//return middle
} else {
max = middle
middle = (max+min)/2
}
}
return middle
}
func getMax(a ...int) int {
var res int = -1 << 31
for _, item := range a {
if item > res {
res = item
}
}
return res
}
func getHour(piles []int, middle int) int {
var res int
for _, item := range piles {
if item%middle > 0 {
res += item/middle + 1
} else {
res += item/middle
}
}
return res
}