算法笔记——二分查找

算法笔记——二分查找

二分查找:用于在有序数列中查找目标元素的位置

关于区间边界的问题

二分法,区间的定义一般为两种,左闭右闭即[left, right],或者左闭右开即[left, right)。

左闭右闭

  1. while (left <= right) 要使用 <= ,因为left == right是有意义的,所以使用 <=
  2. if (nums[middle] > target) right 要赋值为 middle - 1,因为当前这个nums[middle]一定不是target,那么接下来要查找的左区间结束下标位置就是 middle - 1,因为寻找区间是左闭右闭,所以right必须更新为middle-1而不是middle

左闭右开

  1. while (left < right),这里使用 < ,因为left == right在区间[left, right)是没有意义的
  2. if (nums[middle] > target) right 更新为 middle,因为当前nums[middle]不等于target,去左区间继续寻找,而寻找区间是左闭右开区间,所以right更新为middle,middle不包含在区间内

如果可以自己选择区间的话建议使用左闭右闭区间

关于查找的值的问题

这里查找目标值有三种情况

  • 查找给定值
  • 查找小于等于给定值的最大值
  • 查找大于等于给定值的最小值

1.查找给定值

这个比较简单,直接上代码
具体实现细节写在注释中

int BinarySearch(vector<int> nums, int target)
    {
        int n = nums.size();
        int left = 0;
        int right = n - 1; //前闭后闭区间
        while (left <= right)
        {
            int mid = left + (right - left) / 2; //防止直接相加溢出
            if (target == nums[mid])             //找到目标值,直接输出下标
                return mid;
            else if (target > nums[mid]) // target比nums[mid]大,目标值在mid右侧,更新区间为右侧区间
                left = mid + 1;
            else
                right = mid - 1; // target比nums[mid]小,目标值在左侧,更新区间为左侧区间
        }
        return -1; //没有找到目标值,输出-1
    }

2.查找小于等于给定值的最大值

int LowerTarget(vector<int> nums, int target)
    {
        int n = nums.size();
        int left = 0;
        int right = n - 1;   //前闭后闭区间
        while (left < right) //这里是通过不断缩小区间来找要求的值,所以当left==right时,目标值已经找到循环就应该退出了
        {
            int mid = left + (right - left + 1) / 2; //这里要加1,即mid要取高位的数,否则可能出现死循环
            if (nums[mid] <= target)                 // 如果nums[mid]小于等于target,那么最终要求的值可能是mid也可能在mid的左侧,所以left=mid而不是mid+1
                left = mid;
            else
                right = mid - 1; // nums[mid]大于target,应该在左侧区间查找,所以right=mid-1
        }
        //这里left与right相等,用哪个都一样
        if (nums[left] > target) //因为要找的小于等于target的最大值,如果最终得到的值比target还大的话就代表数组中的所有元素都比target大,找不到目标值输出-1
            return -1;
        return left;
    }

这里有三个点需要注意

  1. while(left
  2. int mid=left+(right-left+1)/2
  3. if(nums[mid]<=target) left=mid

具体原因在代码注释中已经写清楚了,第一第三点比较好理解,第二点可以自己带入一组数据试一下

如:在[1,9,25]中找小于等于10的最大值
如果不加第二点

  1. left=0,right=2
  2. mid=1,nums[mid]=9,9
  3. left=1,right=2;mid=1出现死循环

加上第二点后

  1. left=0,right=2
  2. mid=1,nums[mid]=9,9
  3. left=1,right=2,mid=2,nums[mid]=25>target,right=mid-1=1
  4. left=right 退出循环

3.查找大于等于给定值的最小值

这个与上一个类似,对比上一个看即可

int UpperTarget(vector<int> nums, int target)
    {
        int n = nums.size();
        int left = 0;
        int right = n - 1;   //前闭后闭区间
        while (left < right) //这里是通过不断缩小区间来找要求的值,所以当left==right时,目标值已经找到循环就应该退出了
        {
            int mid = left + (right - left) / 2; // mid取低位的数,否则可能出现死循环
            if (nums[mid] >= target)             // 如果nums[mid]大于等于target,那么要求的值可能是mid也可能在mid的左侧,所以right=mid而不是mid-1
                right = mid;
            else
                left = mid + 1; // nums[mid]小于target,应该在右侧区间查找,所以left=mid+1
        }
        //这里left与right相等,用哪个都一样
        if (nums[left] < target) //因为要找的大于等于target的最小值,如果最终得到的值比target还小的话就代表数组中的所有元素都比target小,找不到目标值,输出-1
            return -1;
        return left;
    }

关于C++内置函数的使用

C++中有两个内置函数lower_bound和upper_bound,都是使用二分查找实现的

lower_bound

在从小到大的排序数组中:
lower_bound( begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于或等于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。

在从大到小的排序数组中,重载lower_bound():
lower_bound( begin,end,num,greater() ):从数组的begin位置到end-1位置二分查找第一个小于或等于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。

核心代码:

	while (left < right)
    {
        mid = left + (right - left) / 2;
        if (nums[mid] >= target)
            right = mid;
        else
            left = mid + 1;
    }

upper_bound

在从小到大的排序数组中:
upper_bound( begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。

在从大到小的排序数组中,重载upper_bound():
upper_bound( begin,end,num,greater() ):从数组的begin位置到end-1位置二分查找第一个小于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。

核心代码:

	while (left < right)
    {
        mid = left + (right - left) / 2;
        if (nums[mid] <= target)
            left = mid + 1;
        else
            right = mid;
    }

使用方法

	vector<int> nums1{1, 3, 5, 7};
    cout << *lower_bound(nums1.begin(), nums1.end(), 3);//3
    cout << *upper_bound(nums1.begin(), nums1.end(), 3);//5
    vector<int> nums2{9, 6, 3, 1};
    cout << *lower_bound(nums2.begin(), nums2.end(), 6, greater<int>());//6
    cout << *upper_bound(nums2.begin(), nums2.end(), 6, greater<int>());//3

例题

34. 在排序数组中查找元素的第一个和最后一个位置
33. 搜索旋转排序数组
74. 搜索二维矩阵
162. 寻找峰值

你可能感兴趣的:(算法,算法,排序算法,leetcode)