704. 二分查找

让我们通过一个具体的例子来详细解释二分查找的过程,并通过文本图形化的方式来描述每一步的变化。假设我们有一个数组 nums = [-1, 0, 3, 5, 9, 12] 并且目标值 target = 9

初始状态

  • 数组:[-1, 0, 3, 5, 9, 12]
  • 目标值:9
  • 左指针 left = 0
  • 右指针 right = 5 (数组长度减1)
  • 中间位置 mid = (0 + 5) / 2 = 2 (索引为2的元素是 3)
索引:            0  1  2  3  4   5   
初始数组:        -1  0  3  5  9  12
                  ↑       ↑      ↑
                left     mid    right

第一次循环

  • nums[mid] = 3 小于 target = 9,因此需要调整左指针。
  • 更新左指针 left = mid + 1 = 3
索引:          0  1  2   3  4   5   
第一次循环后:   -1  0  3   5  9  12
                          ↑  ↑  ↑
                        left mid right

第二次循环

  • 更新中间位置 mid = (3 + 5) / 2 = 4 (索引为4的元素是 9)
  • nums[mid] = 9 等于 target = 9,找到目标值,返回索引 4
索引:          0  1  2  3  4   5   
第二次循环后:   -1  0  3  5  9  12
                            ↑
                           mid

结果

  • 目标值 9 的索引为 4

这个文本图形化的描述显示了如何通过二分查找逐步缩小搜索范围,直到找到目标值。在每次循环中,我们都会根据中间值和目标值的比较结果来调整搜索范围,直到成功找到目标值或确定目标值不存在于数组中。

更复杂的例子

让我们考虑一个稍微复杂的情况,以进一步解释二分查找的过程。假设我们有一个数组 nums = [-10, -3, 0, 5, 9, 12, 14, 17, 19, 23, 29] 并且目标值 target = 0

初始状态

  • 数组:[-10, -3, 0, 5, 9, 12, 14, 17, 19, 23, 29]
  • 目标值:0
  • 左指针 left = 0
  • 右指针 right = 10 (数组长度减1)
  • 中间位置 mid = (0 + 10) / 2 = 5 (索引为5的元素是 12)
索引:          0  1  2  3  4   5   6   7   8   9  10
初始数组:     -10 -3  0  5  9  12  14  17  19  23  29
               ↑               ↑                   ↑
              left            mid                right

第一次循环

  • nums[mid] = 12 大于 target = 0,需要调整右指针。
  • 更新右指针 right = mid - 1 = 4
索引:         0   1  2  3  4   5   6   7   8   9  10
第一次循环后: -10 -3  0  5  9  12  14  17  19  23  29
              ↑            ↑
             left        right
               (mid 更新后将位于 left 和 right 的中间)

第二次循环

  • 更新中间位置 mid = (0 + 4) / 2 = 2 (索引为2的元素是 0)
  • nums[mid] = 0 等于 target = 0,找到目标值,返回索引 2
索引:         0  1   2  3  4   5   6   7   8   9  10
第二次循环后: -10 -3  0  5  9  12  14  17  19  23  29
                     ↑
                    mid

结果

  • 目标值 0 的索引为 2

在这个更复杂的例子中,我们看到了二分查找如何在一个更大的数组中有效地定位目标值。通过每次比较中间元素与目标值,并相应地调整搜索范围的边界,二分查找算法能够快速缩小搜索区域,直到找到目标值或确定目标值不在数组中。这种方法特别适用于大型有序数组,因为它大大减少了必须比较的元素数量,从而提高了搜索效率。

代码

下面是一个易于理解的C++代码实现:

class Solution {
public:
    int search(vector<int>& nums, int target) {
        // 初始化两个指针,i(左指针)和j(右指针),分别指向数组的起始和结束位置
        int i = 0, j = nums.size() - 1;
        
        // 当左指针不大于右指针时,执行循环
        while (i <= j) {
            // 计算中间位置的索引mid。这里使用i + (j - i) / 2来避免直接使用(i + j) / 2可能导致的整数溢出问题
            int mid = i + (j - i) / 2;
            
            // 如果中间位置的元素小于目标值,说明目标值在数组的右半部分
            if (nums[mid] < target) {
                // 因此,调整左指针i到中间位置的右侧,即mid + 1
                i = mid + 1;
            } 
            // 如果中间位置的元素大于目标值,说明目标值在数组的左半部分
            else if (nums[mid] > target){
                // 因此,调整右指针j到中间位置的左侧,即mid - 1
                j = mid - 1;
            } 
            // 如果中间位置的元素正好等于目标值
            else {
                // 直接返回该位置的索引
                return mid;
            }
        }
        // 如果循环结束还没有找到目标值,说明目标值不在数组中,返回-1
        return -1;
    }
};

这段代码首先初始化左右指针分别指向数组的开始和结束位置。然后,它进入一个循环,在循环中计算中间位置的索引 mid 并将 mid 位置上的元素与目标值 target 进行比较。如果找到目标值,则返回其索引;如果 mid 位置上的元素小于目标值,则调整左指针 leftmid + 1;如果 mid 位置上的元素大于目标值,则调整右指针 rightmid - 1。如果在数组中找不到目标值,则最终返回 -1