掌握二分查找的关键在于循环不变量
文章目录
在升序数组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的大小,如果相等则mid即为要寻找的下标,如果不相等则根据nums[mid]和target大小关系将查找范围缩小一半。
由于每次查找都会将查找范围缩小一半,因此二分查找的时间复杂度是O(log n), 其中n是数组的长度。二分查找的条件是查找范围不为空,即left<=right (此时查找范围仍然有一个元素). 如果target在数组中,二分查找可以保证找到target, 返回target在数组中的下标。如果target不在数组中,则当left > right时结束查找,返回-1.
二分查找的关键在于循环不变量。
下面实例代码寻找单调不减序列中target最后一次出现的位置。如果target不存在于数组中,则返回小于target的元素最后一次出现的位置。
int serach_right_bound(vector<int>& nums, int target){
int l = 0;
int r = nums.size() - 1;
// [l, r]为待查区间
while(l<=r){
int mid = (l+r)/2;
if(nums[mid] <= target){
l = mid + 1; // l-1一定是小于等于target.
}
else{
r = mid - 1; // r+1一定是大于target.
}
}
//循环结束之后l>r, l=r+1. 故l -1=r. 故nums[r] <= target, nums[r+1]> target. 故r位置即为<=target的元素最后一次出现的位置。
return r;
}
这里: l - 1位置元素一定小于等于target, r+1一定是大于target. 循环结束之后l = r+1, 故返回r.
如果target元素不存在数组中, 则r+1位置的元素是大于target, 故r位置元素即为小于target的某个元素最后一次出现的位置。
参考文献
[1] https://www.bilibili.com/video/BV1AP41137w7/?vd_source=ae9fb55dc7dfe00a427e1a08e73f0577