掌握二分查找的关键在于循环不变量

掌握二分查找的关键在于循环不变量


在升序数组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

猜你喜欢

转载自blog.csdn.net/ChenglinBen/article/details/131832036