Java二分查找笔记 (Binary Search)

3 阅读3分钟

二分查找的核心逻辑是一样的(每次排除一半),但边界的处理方式(即区间的定义)决定了代码细节的不同

1. 左闭右开区间写法 (binarySearchLCRO)

这种写法将搜索区间定义为 [i,j)[i, j) 。这意味着 ii 指向的元素包含在搜索范围内,而 jj 指向的元素不包含在范围内(通常是数组长度)。

public class binary_search左闭右开 {
    /* 二分查找(左闭右开区间) */
    int binarySearchLCRO(int[] nums, int target) {
        //初始化左闭右开区间 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1
        int i = 0 , j = nums.length;
        while(i < j){
            int m = i + (j - i) / 2;
            if (nums[m] < target){
                i = m + 1;
            } else if (nums[m] > target) {
                j = m;
            }
            else {
                return m;
            }
        }
        return -1;
    }
}

初始化:

  • int i = 0, j = nums.length;
  • i 指向数组第一个元素。
  • j 指向数组最后一个元素的后面(即数组长度)。因为是“右开”,所以 jj 这个下标是取不到的

循环条件:

  • while(i < j)
  • 这里使用 < 而不是 <=
  • 原因:i==ji == j 时,区间 [i,i)[i, i) 是一个空集,没有任何元素,所以循环应该终止。

边界更新:

  • if (nums[m] < target) (目标在右侧):

    • i = m + 1;
    • mm 已经被检查过且太小,所以左边界 ii 必须移到 mm 的右边。
  • if (nums[m] > target) (目标在左侧):

    • j = m;
    • 注意: 这里是 j = m 而不是 m - 1
    • 原因: 因为区间是“右开”的,即 [i,j)[i, j) 不包含 jj。既然 nums[m]nums[m] 已经确定大于 target,它就不可能是目标值。我们将 jj 设为 mm,表示下一次搜索的范围截止到 mm 之前(不含 mm),这符合逻辑。

2. 双闭区间写法 (binarySearch)

这是最常见、最容易理解的教科书写法。它将搜索区间定义为 [i,j][i, j] 。这意味着 iijj 指向的元素都包含在搜索范围内

package Algorithm.Search;
import java.util.Arrays;

public class binary_search {

    public static void main(String[] args) {
        // 二分查找的数组必须是有序的
        int[] nums = {1,3,5,6,8,2};
        Arrays.sort(nums);
        int n = binarySearch(nums,1);
        System.out.println(n);


    }
    /* 二分查找(双闭区间) */
    static int binarySearch(int[] nums , int target){
        int i = 0 ,  j = nums.length - 1;
        //循环
        while(i <= j){
            int m = i + (j - i)/2;
            if (nums[m] < target){
                i = m + 1;
            } else if (nums[m] > target) {
                j = m - 1;
            }
            else {
                return m;
            }
        }
        //// 未找到目标元素,返回 -1
        return -1;
    }
}
  • 初始化:

    • int i = 0, j = nums.length - 1;
    • i 指向第一个元素。
    • j 指向最后一个元素。因为是“双闭”,这两个下标都是有效的。
  • 循环条件:

    • while(i <= j)
    • 这里必须使用 <=
    • 原因:i==ji == j 时,区间 [i,i][i, i] 依然包含一个元素(即 nums[i]nums[i]),这个元素还没有被检查过,所以必须进入循环再检查一次。如果用 <,就会漏掉这最后一个元素。
  • 边界更新:

    • if (nums[m] < target) (目标在右侧):

      • i = m + 1;
      • 同上,左边界向右收缩。
    • if (nums[m] > target) (目标在左侧):

      • j = m - 1;
      • 注意: 这里是 j = m - 1
      • 原因: 因为 jj 是包含在搜索范围内的(闭区间)。既然 nums[m]nums[m] 已经确认太大,那么 mm 这个位置肯定不是目标。为了下一次搜索不包含它,右边界必须退格到 m1m - 1