二分法查找

二分法查找算法应用的条件:

  • 数组按照顺序排列【基础】;
  • 数组中没有重复的元素【否则返回值元素的下标不唯一】;

二分法查找主要难点在于边界条件的确定,常见的区间的定义一般有两种:左闭右闭,即 [left, right],或者左闭右开,即 [left, right);

第一种:左闭右闭,即 [left, right],代码如下:

class Solution {
    public int search(int[] nums, int target) {
        int res = -1;
        int right = nums.length -1;
        int left = 0;
        while (left <= right) {            //重点在于循环条件的确定;
            int middle = left + ((right - left) >> 1);
            if (nums[middle] > target) {
                right = middle -1;
            }
            else if (nums[middle] < target) {
                left = middle +1;
            }
            else {
                res = middle;
                break;
            }
        }
        return res;
    }
}

第二种:左闭右开,即 [left, right),代码如下:

class Solution {
    public int search(int[] nums, int target) {
        int res = -1;
        int right = nums.length;
        int left = 0;
        while (left < right) {				//循环条件的确定
            int middle = left + ((right - left) >> 1);
            if (nums[middle] > target) {
                right = middle;
            }
            else if (nums[middle] < target) {
                left = middle +1;
            }
            else {
                res = middle;
                break;
            }
        }
        return res;
    }
}

两种方法的区别:

  • 左闭右闭,即 [left, right],
    • 当left = right时,仍然要进入循环;
    • 当没有找到 target 值时,循环体结束后,left - right = 1
  • 左闭右开,即 [left, right),
    • 当left = right时,跳出循环;
    • 当没有找到 target 值时,循环体结束后,right = left

此外,涉及到边界条件问题:

  • 例如:在排序数组中查找元素的范围【数组非严格单调,存在重复元素】,此时,需要确定范围的左右区间;

  • 区间的确定:

    \[左区间:nums[pos−1]<target≤nums[pos]\\ 右区间:nums[pos−1]≤target<nums[pos] \]

  • 当为左闭右闭,即 [left, right]时:

    public int getRight(int[] nums, int target) {
            int res = -1;
            int right = nums.length - 1;
            int left = 0;
            while (right >= left) {
                int middle = left + ((right - left) >> 1);
                if (nums[middle] > target) {
                    right = middle -1;
                }
                else {
                    left = middle + 1;
                }
            }
            res = left;
            }
            return res;
        }
    public int getLeft(int[] nums, int target) {
            int res = -1;
            int right = nums.length - 1;
            int left = 0;
            while (right >= left) {
                int middle = left + ((right - left) >> 1);
                if (nums[middle] < target) {
                    left = middle + 1;
                }
                else {
                    right = middle -1;
                }
            }
            res = right;
            return res;
        }
    
    • 循环体结束后,left - right = 1

    • 当数组不存在对应target元素时,左右区间产生的left,right完全一样;

    • 当数组中存在一个元素时

      • left - right = 1

      • \(right_{左区间}\) = \(left_{右区间}\)

      • 由上面两个公式,可得:

        \(right_{右区间} - left_{左区间}=2\)

    • 当数组中存在多个元素时,同理:

      • \(right_{右区间} - left_{左区间}=2 + 元素个数\)
    • 统一范围:[\(left_{左区间} +1, right_{右区间} -1\)],

  • 当为左闭右开,即 [left, right)时,代码如下:

        public int getRight(int[] nums, int target) {
            int res = -1;
            int right = nums.length;
            int left = 0;
            while (right > left) {
                int middle = left + ((right - left) >> 1);
                if (nums[middle] > target) {
                    right = middle;
                }
                else {
                    left = middle + 1;
                }
            }
            res = left;
            return res;
        }
        public int getLeft(int[] nums, int target) {
            int res = -1;
            int right = nums.length;
            int left = 0;
            while (right > left) {
                int middle = left + ((right - left) >> 1);
                if (nums[middle] < target) {
                    left = middle + 1;
                }
                else {
                    right = middle;
                }
            }
            res = right;
            return res;
        }
    
    • 循环体结束后,left = right,并且最后一步骤基本都是 left = left + 1
    • 当数组不存在对应target元素时,
      • target元素在数组的范围内,但不存在时,left = right=靠近较大值的索引,例如:nums = [0, 0, 2, 2],target = 1, \(left = right = 2_{index}\)
      • target元素不在数组的范围内,且不存在时,left = right = 0 or nums.length
    • 当数组中存在一个元素时:
      • 左区间:$left_{右区间} = right_{右区间} $ = 元素索引;
      • 右区间: $left_{右区间} = right_{右区间} $ = 元素索引 + 1;
      • 因为当left和right相邻的时候,middle取的是 left的值作为索引,而左闭右开,则还会执行一次;
    • 当数组中存在多个元素时,
      • 左区间:$left_{右区间} = right_{右区间} $ = 元素索引;
      • 右区间: $left_{右区间} = right_{右区间} $ = 元素索引 + 元素个数;
    • 统一范围:[$left_{左区间} , right_{右区间} -1 $];
posted @ 2025-01-08 20:33  烟雨断桥  阅读(68)  评论(0)    收藏  举报