二分查找 (Binary Search)

0 阅读3分钟

1. 原理解析

二分查找,又名折半查找。 生活中的例子:假设我们在玩一个猜数字游戏,数字范围是 1 到 100。我心里想了一个数字,你每次猜一个数字,我会告诉你“大了”、“小了”还是“猜对了”。 如果你从 1 开始挨个猜(1, 2, 3...),最坏情况要猜 100 次。但如果你很聪明,你会先猜 50:

  • 如果我说“大了”,你就知道答案在 1 到 49 之间。
  • 下一次你猜 25,不断折半。 这就是二分查找的核心思想:每次排除一半的错误答案,极大地缩小搜索范围

2. 使用场景

  • 前提条件:数据必须是有序的(从小到大或从大到小),并且支持随机访问(比如数组)。
  • 典型场景:在大量有序数据中快速定位某个特定的值,或者寻找满足某个条件的边界(如第一个大于某数的元素)。

3. 核心难点/易错点详解

二分查找看起来简单,但写对很难(被誉为“十个二分九个错”)。核心难点在于循环边界区间更新

关键点:明确你定义的“搜索区间”是什么。 通常我们定义搜索区间为“左闭右闭” [left, right]

  • 初始化:left = 0, right = array.length - 1 (因为索引 length-1 是可以取到的)。
  • 循环条件:while (left <= right)为什么是 <= 因为当 left == right 时,区间内还有一个元素没检查,比如区间变成了 [3, 3],你得把索引为 3 的元素检查一下。如果用 <,就会漏掉这个元素。
  • 更新区间:
    • 如果 array[mid] > target,说明目标在左半边。此时新的右边界应该是 right = mid - 1(因为 mid 已经检查过不是目标了,直接剔除)。
    • 如果 array[mid] < target,说明目标在右半边。新的左边界 left = mid + 1

防溢出小技巧: 计算中间位置时,不要写 mid = (left + right) / 2。如果 leftright 都很大,相加可能会超出整型的最大值范围导致溢出。 正确写法:mid = left + (right - left) / 2。也可以理解为“左端点加上区间长度的一半”。

4. 代码实战

4.1 左闭右闭写法 [left, right]

Python 实现:

def binary_search(nums: list[int], target: int) -> int:
    left, right = 0, len(nums) - 1
    
    while left <= right:
        mid = left + (right - left) // 2
        
        if nums[mid] == target:
            return mid  
        elif nums[mid] < target:
            left = mid + 1  
        else:
            right = mid - 1 
            
    return -1 

Java 实现:

public class BinarySearch {
    public int search(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;

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

4.2 左闭右开写法 [left, right) (补充拓展)

Python 实现:

def binary_search_open(nums: list[int], target: int) -> int:
    left, right = 0, len(nums) # right 取不到 len(nums)
    
    # 因为 right 取不到,所以 left == right 时区间为空,循环条件不能带等号
    while left < right:
        mid = left + (right - left) // 2
        
        if nums[mid] == target:
            return mid
        elif nums[mid] < target:
            left = mid + 1
        else:
            right = mid # 右边界取不到,设为 mid 刚好排除 mid 本身
            
    return -1

Java 实现:

public class BinarySearchOpen {
    public int search(int[] nums, int target) {
        int left = 0;
        int right = nums.length; 

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

5. 复杂度分析

  • 时间复杂度O(logN)O(\log N)。每次循环将搜索范围缩小一半,假设总共有 NN 个元素,最多需要找 log2(N)\log_2(N) 次。这是极其高效的(比如 40 亿个数字中找一个,最多只需 32 次)。
  • 空间复杂度O(1)O(1)。我们只使用了几个整型变量(left, right, mid),没有额外申请数组空间。