二分搜索算法

26 阅读2分钟

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

二分搜索算法是一种在有序数组中查找某一特定元素的算法。但是二分搜索并非仅使用在有序数组中(看似与前句矛盾,但这种情况下篇文章详述)。

它的优点是非常高效,时间复杂度为O(logn)

二分搜索的解题思路:

  1. 确定搜索的范围和区间边界
  2. 求中间值看是否满足条件,若满足返回
  3. 若不满足,则根据判断确定在中间值的左边还是右边进行搜索

二分搜索算法通常有两种解法:递归或者循环

下面用几道例题熟悉一下:

33.搜索旋转排序数组

依据题意,可知虽然该数组整体并非有序,但是只要选定中点,那么一定有一侧是有序的,根据这个规律,即可进行二分搜索:

  • nums[mid]>nums[left],左边为有序
  • nums[mid]<nums[left],右边为有序
var search = function(nums, target) {
    let l = 0, r = nums.length - 1
    while (l <= r) {
        let mid = l + ((r - l) >> 1)
        let midVal = nums[mid]
        if (midVal === target) {
            return mid
        }
        // 如果左边有序
        if (midVal >= nums[l]) {
            if (midVal > target && nums[l] <= target) {
                r = mid - 1
            } else {
                l = mid + 1
            }
            continue
        }
        // 如果左边无序,则右边有序
        if (midVal < target && nums[r] >= target) {
            l = mid + 1
        } else {
            r = mid - 1
        }
    }
    return -1
};

image.png

34. 在排序数组中查找元素的第一个和最后一个位置

  • 依据题意,该数的第一个位置,必须满足以下条件,以target为6为例
    • 这个位置左边必须小于8,或者左边没有数
  • 该数的第二个位置,必须满足以下条件
    • 这个位置右边必须大于8,或者右边没有数
var searchRange = function(nums, target) {
    let l = 0, r = nums.length - 1
    let leftIndex = rightIndex = -1
    while (l <= r) {
        let mid = l + ((r - l) >> 1)
        if (nums[mid] === target) {
          leftIndex = mid
          r = mid - 1
        } else if (nums[mid] > target) {
          r = mid - 1
        } else {
          l = mid + 1
        }
    }
    l = 0, r = nums.length - 1
    while (l <= r) {
        let mid = l + ((r - l) >> 1)
        if (nums[mid] === target) {
          rightIndex = mid
          l = mid + 1
        } else if (nums[mid] > target) {
          r = mid - 1
        } else {
          l = mid + 1
        }
    }
    return [leftIndex, rightIndex]
};

image.png

当然,以上解法是有些冗余的,为了找到左边界和右边界,分别去遍历,其实逻辑差不多,那么有没有办法优化呢,我们可以想到,首先,一次二分搜索肯定能找到等于target的值,找到后判断其左边和右边是否也等于target,然后更新左边界和右边界即可,代码如下

var searchRange = function(nums, target) {
    let l = 0, r = nums.length - 1, mid
    while (l <= r) {
        mid = l + ((r - l) >> 1)
        if (nums[mid] === target) {
           break
        } else if (nums[mid] > target) {
          r = mid - 1
        } else {
          l = mid + 1
        }
    }
    if (l > r) {
        return [-1, -1]
    }
    let leftIndex = rightIndex = mid
    while (nums[leftIndex - 1] === target) leftIndex--
    while (nums[rightIndex + 1] === target) rightIndex++
    return [leftIndex, rightIndex]
};