二分查找算法

零、例题链接

二分查找

一、使用条件

  1. 含有n个元素的nums数组元素值非单调递减

二、核心思想

  1. 循环:将target与区间内中间位置值比较,对半缩小可能包含target的区间长度【比较大小后,target在区间左/右半边的概率为0】

三、算法关键步骤

  1. 第一步
    1. 设置left、right索引初始值
      1. left、right索引初始值设置的不同,决定了可能包含target的区间形式
      2. 可能包含target的区间形式有四种
        1. 左开右开
          1. left和right均为开区间边界,即target可能在区间内,但一定不是left和right指向的元素(或target可能所在的数组索引一定不是left和right)。
        2. 左闭右闭【常用】
          1. left和right均为闭区间边界,即target可能在包含 “left和right指向的元素” 的区间内
        3. 左开右闭
          1. target可能在包含 “right指向的元素” 的区间内,但left指向的元素一定不是target
        4. 左闭右开【常用】
          1. target可能在包含 “left指向的元素” 的区间内,但right指向的元素一定不是target
  2. 第二步
    1. 判断while循环条件
      1. 循环条件的选取,取决于可能包含target的区间形式
        1. 左开右开:left < right - 1
          1. 当left = right - 1时,可能包含target的区间内元素数量为0
        2. 左闭右闭:left <= right
          1. 当left > right时,可能包含target的区间内元素数量为0
        3. 左开右闭:left < right
          1. 当left = right时,可能包含target的区间内元素数量为0
        4. 左闭右开:left < right
          1. 当left = right时,可能包含target的区间内元素数量为0
  3. 第三步
    1. 进入循环后,只需要保证两点
        1. 无论计算前后,保证区间内的元素没被判断过【这决定了left、right重赋值的方式】
          1. 重点注意:计算后,nums[ mid ]属于被判断过的元素
        1. 无论向上/向下取整计算mid索引,保证mid索引始终指向区间内的某一元素【这决定了mid的计算方式】

四、代码示例

class Solution {
public:
    int search(vector<int>& nums, int target) {
        //情况一:左开右开
        /***********************重点注意***********************/
        int left1 = -1, right1 = nums.size();  //决定了可能包含target的区间形式为:左开右开
        /*****************************************************/
        int mid1 = 0; 
        while(left1 < right1 - 1) {
            mid1 = left + (right - left) / 2;  //防止加法溢出
            if(nums[mid1] > target)
                right1 = mid1;
            else if(nums[mid1] == target)
                return mid1;
            else if(nums[mid1] < target)
                left1 = mid1;
        }

        //情况二:左闭右闭
        /***********************重点注意***********************/
        int left2 = 0, right2 = nums.size() - 1;  //决定了可能包含target的区间形式为:左闭右闭
        /*****************************************************/
        int mid2 = 0; 
        while(left2 <= right2) {
            mid2 = (left2 + right2) / 2;
            if(nums[mid2] > target)
                right2 = mid2 - 1;
            else if(nums[mid2] == target)
                return mid2;
            else if(nums[mid2] < target)
                left2 = mid2 + 1;
        }

        //情况三:左闭右开
        /***********************重点注意***********************/
        int left3 = 0, right3 = nums.size();  //决定了可能包含target的区间形式为:左闭右开
        /*****************************************************/
        int mid3 = 0; 
        while(left3 < right3) {
            mid3 = (left3 + right3) / 2;
            if(nums[mid3] > target)
                right3 = mid3;
            else if(nums[mid3] == target)
                return mid3;
            else if(nums[mid3] < target)
                left3 = mid3 + 1;
        }

        //情况四:左开右闭:向上取整
        //使用向上取整的原因:向下取整可能会将mid指向左开区间边界值,又因为我们限定左开右闭,即mid不可指向左边界值,因此mid的指向矛盾,故采用向上取整。
        /***********************重点注意***********************/
        int left4 = -1, right4 = nums.size() - 1;  //决定了可能包含target的区间形式为:左开右闭
        /*****************************************************/
        float temp = 0.0;
        int mid4 = 0; 
        while(left4 < right4) {
            temp = left4 + right4;
            mid4 = ceil(temp / 2);  //ceil():向上取整
            if(nums[mid4] > target)
                right4 = mid4 - 1;
            else if(nums[mid4] == target)
                return mid4;
            else if(nums[mid4] < target)
                left4 = mid4;
        }
        return -1;
    }
};

五、相关习题

  1. 34.在排序数组中查找元素的第一个和最后一个位置
posted @ 2025-02-20 10:29  宇星海  阅读(22)  评论(0)    收藏  举报