二分搜索算法2

79 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第25天,点击查看活动详情

在上文中,我们说二分搜索的定义是在有序数组中找到某个数,但是也提了一句,并非全是在有序数组中才能用二分法,那是因为,有一种特殊的无序数组,可以用二分搜索算法解决。

二分搜索算法可以解决的问题有:

  1. 在有序数组中找某个数是否存在
  2. 在有序数组中 找大于等于某个数 最左侧位置
  3. 无序数组中,任何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
};

image.png

结论是,在无序数组中,只要能把左右两边区分开,判断选择左边还是右边,就可以用二分搜索算法