- 所谓二分查找就是指通过不断二分,比较边界值与中值大小,最终不断逼近并找到目标值的算法。
- 二分查找有两种主要形式,[left,right] 和 [left,right)。
力扣 704 是简单二分查找典型题:
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果 target 存在返回下标,否则返回 -1。
你必须编写一个具有 O(log n) 时间复杂度的算法。
- 你可以假设
nums中的所有元素是不重复的。 n将在[1, 10000]之间。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;
}
};
总结
- 二分查找的关键在于确定好边界,对区间的定义理解清楚自然能形成代码。
- 两个模板的主要区别是:
| 类型 | right初始值 | left与right大小 | left与right更新值 |
|---|---|---|---|
| 左闭右闭 | nums.size( )- 1 | left <= right | left = mid + 1; right = mid - 1 |
| 左闭右开 | nums.size( ) | left < right | left = mid + 1 right = mid |