携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第25天,点击查看活动详情
在上文中,我们说二分搜索的定义是在有序数组中找到某个数,但是也提了一句,并非全是在有序数组中才能用二分法,那是因为,有一种特殊的无序数组,可以用二分搜索算法解决。
二分搜索算法可以解决的问题有:
- 在有序数组中找某个数是否存在
- 在有序数组中 找大于等于某个数 最左侧位置
- 无序数组中,任何2个相邻数不相等,求任一局部最小值问题
那本文就举例详细探讨一下第3种情况
162. 寻找峰值
首先注意题目条件:
- 对于所有有效的
i都有nums[i] != nums[i + 1],相邻两个数一定不相等 - 你必须实现时间复杂度为
O(log n)的算法来解决此问题。那么可以想到二分法 - 找到一个mid
- 如果
nums[mid] > nums[mid-1]且nums[mid] > nums[mid + 1]则这个mid是峰值 - 如果
nums[mid+1] > nums[mid] > nums[mid-1],则峰值在右区间 - 如果
nums[mid+1] < nums[mid] < nums[mid-1],则峰值在左区间 - 如果
nums[mid] < nums[mid-1]且nums[mid] < nums[mid+1],则这个mid是峰谷,往左往右都可以
- 如果
var findPeakElement = function(nums) {
if (!nums.length) return -1
let l = 0, r = nums.length - 1
while (l <= r) {
let mid = l + ((r - l) >> 1)
if (mid === l || nums[mid - 1] < nums[mid]) {
if (mid === r || nums[mid] > nums[mid + 1]) {
return mid
}
l = mid + 1
} else {
r = mid - 1
}
}
return -1
};
上述算法还可以优化
- 关键点在于左右边界,都是负无穷,那么只要相邻元素高的那边,继续走肯定能遇到下坡,就能找到峰值
var findPeakElement = function(nums) {
let left = 0, right = nums.length - 1
while (left < right) {
let mid = left + ((right - left) >> 1)
// 贪心二分,如果下降则左侧必有峰
if (nums[mid] > nums[mid+1]) {
// mid有可能是峰值,那么right不能是mid-1
right = mid
} else {
// 如果上升则右侧必有峰
left = mid + 1
}
}
// 这里返回left/right都可以,相等才会走到
return right
};
结论是,在无序数组中,只要能把左右两边区分开,判断选择左边还是右边,就可以用二分搜索算法