二分查找

4 阅读3分钟

  1. 所谓二分查找就是指通过不断二分,比较边界值与中值大小,最终不断逼近并找到目标值的算法。
  2. 二分查找有两种主要形式,[left,right] 和 [left,right)。

力扣 704 是简单二分查找典型题:

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target  ,写一个函数搜索 nums 中的 target,如果 target 存在返回下标,否则返回 -1。 你必须编写一个具有 O(log n) 时间复杂度的算法。

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

模板1 左闭右闭

❌下面是关于左闭右闭情形的初次代码:

int BinarySearch(vector<int>&nums,int target){
    int left = 0;
    int right = nums.size() - 1;
    while(left <= right){
        int mid = (right + left) / 2;
        if(nums[mid] == target)
            return mid;
        else if(nums[mid] > target)
            right = nums[mid] - 1;
        else
            left = nums[mid] + 1;        
    }
return -1;
}

代码中存在两个关键超时问题:

(1) mid 使用(left+right)/ 2 定义,容易出现 int 边界溢出。

(2) else 语句中错把 nums[mid] 当成 mid 并以此更新边界值,导致死循环。

✅更正后符合力扣格式的代码:

class Solution {
public:
    int search(vector<int>&nums,int target){
    int left = 0;
    int right = nums.size() - 1;
    while(left <= right){
        int mid = left + (right - left) / 2;
        if(nums[mid] == target)
            return mid;
        else if(nums[mid] > target)
            right = mid - 1;
        else
            left = mid + 1;        
    }
return -1;
}
};

为什么原mid计算方式会出现超时死循环?

int有最大值限制,当数组长度接近10000时,left 和 right 相加会超出 int 存储范围,发生数值溢出,造成错误: (1)正数相加溢出后变成负值 (2)mid 取负值,nums 数组下标为负,访问后读取非法内存,造成混乱 (3)区间收缩失效,陷入死循环


模板2 左闭右开

❌下面是关于左闭右开的初次代码:

class Solution {
public:
    int search(vector<int>&nums,int target){
    int left = 0;
    int right = nums.size() - 1;
    while(left < right){
        int mid = left + (right - left) / 2;
        if(nums[mid] == target)
            return mid;
        else if(nums[mid] > target)
            right = mid ;
        else
            left = mid + 1;        
    }
return -1;
}
};

运行后发现测试用例有一部分未通过,问题是什么?

实际上左闭右开的形式下,right 就不再等于数组长度数减 1 ,而是直接等于数组长度数。如果还等于长度数减一,就会出现一部分用例无法进入循环。比如当数组只有单个元素 [5] ,如果是减一,那 left = 0,right = 0 ,left < right == 0 < 0 不成立,直接跳过循环返回 -1,造成部分测试用例不通过。

✅更正后符合力扣格式的代码:

class Solution {
public:
   int search(vector<int>&nums,int target){
    int left = 0;
    int right = nums.size() ;
    while(left < right){
        int mid = (right + left) / 2;
        if(nums[mid] == target)
            return mid;
        else if(nums[mid] > target)
            right = mid ;
        else
            left = mid + 1;        
    }
return -1;
}
};

总结

  1. 二分查找的关键在于确定好边界,对区间的定义理解清楚自然能形成代码。
  2. 两个模板的主要区别是:
类型right初始值left与right大小left与right更新值
左闭右闭nums.size( )- 1left <= rightleft = mid + 1; right = mid - 1
左闭右开nums.size( )left < rightleft = mid + 1 right = mid