LeetCode —— 704. 二分查找

227 阅读3分钟

启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第1天,点击查看活动详情

近日,在LeetCode上刷每日一题时,有感自身积累不足,面对简单和中等的题型往往很快会有思路,并且能不断的优化,但是面对困难题型便束手无策了,于是制定刷题顺序,进行连贯性刷题。

题目来源

704. 二分查找 (LeetCode)

题目描述(简单

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target  ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1

示例1

输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4

示例2

输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1

提示

  • 你可以假设 nums 中的所有元素是不重复的。
  • n 将在 [1, 10000]之间。
  • nums 的每个元素都将在 [-9999, 9999]之间。

题目解析

二分查找

该题是一道原汁原味的二分搜索法,并且该题的前提是数组为有序数组,并且强调了数组无重复元素,因为一旦有重复元素,使用二分查找法返回的元素下标可能不是唯一的。

这些都是二分查找法的前提条件,当题目描述满足以上条件时,就可以考虑二分查找法了。

同时,二分查找法的关键在于边界条件的处理

例如

  • while (left < right)while (left < =right)
  • right = middleright = middle - 1

到底选择哪一个呢

这时只需要坚持循环不变量规则,即在循环中每一次边界的处理都要坚持根据 区间的定义(不变量(左闭右闭或者左闭右开)) 来操作。

根据题意,在升序数组 nums 中寻找目标值 target ,对于特定下标 i ,比较 nums[i]target 的大小:

  • 如果 nums[i] = target ,则下标 i 为要寻找的小标
  • 如果 nums[i] > target ,则 target 只可能出现在下标 i 的左侧
  • 如果 nums[i] < target ,则 target 只可能出现在下标 i 的右侧

左闭右闭

定义查找的区间 [left,right] ,每次取查找范围的中点 mid ,比较 nums[mid]target 的大小,依据上述事实,返回下标或者缩小范围。

此时区间为左闭右闭,你会发现在此区间中 left === right 的情况是有意义的,所以边界条件如下:

  • while (left <= right)
  • right = mid - 1
  • left = mid + 1
  • right = nums.length - 1

代码

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number}
 */
var search = function(nums, target) {
    let left = 0, right = nums.length - 1
    while (right >= left) {
        let mid = Math.floor((left + right + 1) / 2)
        if (nums[mid] > target) {
            right = mid - 1
        }else if(nums[mid] < target){
            left = mid + 1
        }else{
            return mid
        }
    }
    return -1
};

如图:

image.png

image.png

左闭右开

定义查找的区间 [left,right] ,每次依旧取查找范围的中点 mid

此时区间为左闭右开,你会发现此区间中 left === right 是没有意义的。所以边界条件如下:

  • while (left < right)
  • right = nums.length
  • left = mid + 1
  • right = mid

代码

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number}
 */
var search = function(nums, target) {
    let left = 0, right = nums.length
    while (right > left) {
        let mid = Math.floor((left + right) / 2)
        if (nums[mid] > target) {
            right = mid
        }else if(nums[mid] < target){
            left = mid + 1
        }else{
            return mid
        }
    }
    return -1
};

如图:

image.png

当然还有左开右闭的情况,不过这种情况十分少见,也依旧遵循循环不变量规则,感兴趣的小伙伴可以尝试一下,这里不再赘述了。

相关题目推荐

执行用时和内存消耗仅供参考,大家可以多提交几次。如有更好的想法,欢迎大家提出。