数据结构与算法篇-二分查找-获取目标元素的位置

二分查找 -- 获取目标元素的位置

问题描述

输入:数组 arr=[11, 12, 22, 25, 64, 90],目标元素 target=64

输出:若存在,返回目标元素在数组中的位置;否则,返回 -(left+1)

示例1:

  • 数组 arr=[11, 12, 22, 25, 64, 90],目标元素 target=64
  • 输出:4

示例2:

  • 数组 arr=[11, 12, 22, 25, 64, 90],目标元素 target=100
  • 输出:-7

请思考:

  • 返回值为什么不是-left
  • 返回值为什么不是-1?
  • 当目标元素查找失败时,left值的含义是什么?

状态定义

findRecursive(arr, target, left, right) 表示

  • 目标元素 target 在区间arr[left, right]中的位置,或者-(left+1)

基准情况:

  • left > right 时,区间arr[left,right] 为空,目标元素target显然不在其中,返回值为 -(left+1)。

推导递推关系

要计算 findRecursive(arr, target, left, right), 让我们考虑区间arr[left, right] 的中间元素arr[mid],其中mid=left+(right-left)/2

有 3 种情况

target == arr[mid]:查找成功,返回 mid

target < arr[mid]:目标元素在左半边,等价于调用 findRecursive(arr, target, left, mid-1)

target > arr[mid]:目标元素在右半边,等价于调用 findRecursive(arr, target, mid + 1, right)

请总结下递推关系:

txt 复制代码
findRecursive(arr, target, left, right) =
    if left > right
        -(left+1)
    else if arr[mid] == target
        mid
    else if target < arr[mid]
        findRecursive(arr, target, left, mid - 1)
    else
        findRecursive(arr, target, mid + 1, right)

其中 mid = left + (right - left) / 2

朴素递归解

java 复制代码
public static int findRecursive(int[] arr, int target){
    return findRecursiveHelper(arr, target, 0,  arr.length - 1);
}

private static int findRecursiveHelper(int[] arr, int target, int left, int right) {
    if (left > right) {
        return -(left+1);
    }

    int mid = left + (right - left) / 2;

    if (target == arr[mid]) {
        return mid;
    } else if (target < arr[mid]) {
        return findRecursiveHelper(arr, target, left, mid - 1);
    } else {
        return findRecursiveHelper(arr, target, mid + 1, right);
    }
}

迭代解

java 复制代码
public static int findIterative(int[] arr, int target){
    int left = 0;
    int right = arr.length - 1;

    while (left <= right) {
        int mid = left + (right - left) / 2;

        if (target == arr[mid]) {
            return mid;
        } else if (target < arr[mid]) {
            right = mid - 1;
        } else {
            left = mid + 1;
        }
    }
    return -(left+1);
}

思考题

请思考:

  • 当目标元素查找失败时,left值的含义是什么?
  • 返回值为什么不是-left
  • 返回值为什么不是-1?

追踪执行流程

结论:当目标元素查找失败时,left值编码了插入点的位置信息。

txt 复制代码
数组:[11, 12, 22, 25, 64, 90]

查找:22
mid = left + (right - left) / 2

Step 1: left=0, right=5, mid=2
data[2] = 22 == 22, 找到了,返回值:2

查找:11
mid = left + (right - left) / 2

Step 1: left=0, right=5, mid=2
data[2] = 22 > 11, 查找左半边

Step 2: left=0, right=1, mid=0
data[0] = 11 == 11, 找到了,返回值:0

查找:8
mid = left + (right - left) / 2

Step 1: left=0, right=5, mid=2
data[2] = 22 > 8, 查找左半边

Step 2: left=0, right=1, mid=0
data[0] = 11 > 8, 查找左半边

Step 3: left=0, right=-1 left > right, exit loop
返回值:?

查找:90
mid = left + (right - left) / 2

Step 1: left=0, right=5, mid=2
data[2] = 22 < 90, 查找右半边

Step 2: left=3, right=5, mid=4
data[4] = 64 < 90, 查找右半边

Step 3: left=5, right=5, mid=5
data[5] = 90 == 90, 找到了,返回 5


查找:92
mid = left + (right - left) / 2

Step 1: left=0, right=5, mid=2
data[2] = 22 < 92, 查找右半边

Step 2: left=3, right=5, mid=4
data[4] = 64 < 92, 查找右半边

Step 3: left=5, right=5, mid=5
data[5] = 90 < 92, 查找右半边

Step 4: left=6, right=5, left > right, exit loop
data[5] = 90 < 92, 查找右半边
返回值:?

当目标元素查找失败时,left 的含义是什么?

当二分查找失败时,left 指向的是 "目标元素如果要插入数组,应该插入的位置(插入点)"

为什么是插入点?

在整个二分过程中,你始终维护着一个不变量

区间 [left, right]target 可能存在的唯一区间

当算法终止时:

txt 复制代码
left > right

这意味着什么?

  • [left, right] 这个区间已经 为空
  • 所有 < left 的位置,其元素 都 < target
  • 所有 >= left 的位置,其元素 都 > target

👉 因此:

left 恰好是数组中第一个 ≥ target 的位置。也就是:插入 target 后,数组仍然有序的位置

查找 8(比最小值还小)
txt 复制代码
最终:left = 0, right = -1
  • 插入点应为索引 0
  • left = 0 ✔️
查找 92(比最大值还大)
txt 复制代码
最终:left = 6, right = 5
  • 插入点应为数组末尾(index = 6)
  • left = 6 ✔️

无论 target 落在哪,失败时的 left 都精确编码了插入位置

二、返回值为什么不是 -left

这是一个编码冲突问题

核心要求

返回值必须同时表达两件事:

  1. 查找是否成功
  2. 如果失败,插入点在哪里
如果返回 -left 会发生什么?
冲突情况 1:left = 0
txt 复制代码
返回 -0 = 0

0 是一个合法索引

  • 你无法区分:

    • 「找到了,位置在 0」
    • 「没找到,插入点在 0」

信息丢失

-(left + 1) 如何避免冲突?
txt 复制代码
返回值 = -(left + 1)
left 返回值 是否可能与合法索引冲突
0 -1 ❌ 不可能
1 -2
4 -5

所有失败返回值都严格小于 0

反向解码也很优雅
txt 复制代码
insertionPoint = -(returnValue + 1)

这就是 Java Arrays.binarySearch 的设计原因。

返回值为什么不是 -1

因为:

-1 只能表示失败,无法表示"失败发生在哪里"

查找 8 和 92

如果统一返回 -1

txt 复制代码
查找 8  -> -1
查找 92 -> -1

你完全不知道:

  • 插入点是 0
  • 还是 6

-(left+1) 能区分:

target left 返回值
8 0 -1
92 6 -7

信息完整、可逆、无歧义。

一句话总结

二分查找失败时,left 精确表示"插入点"。

-(left+1) 是一种不与合法索引冲突、且可逆的信息编码方式


相关推荐
nju_spy2 天前
力扣每日一题 2026.1
算法·leetcode·二分查找·动态规划·最小生成树·单调栈·最长公共子序列
haoly19894 天前
数据结构与算法篇-线性查找-存在性问题
线性查找·递归分析
程序员-King.8 天前
day128—二分查找—搜索二维矩阵(LeetCode-74)
leetcode·矩阵·二分查找
Tisfy9 天前
LeetCode 3453.分割正方形 I:二分查找
算法·leetcode·二分查找·题解·二分
程序员-King.10 天前
day126—二分查找—寻找旋转排序数组中的最小值(LeetCode-153)
算法·leetcode·二分查找
闻缺陷则喜何志丹17 天前
【二分查找 图论】P10206 [JOI 2024 Final] 建设工程 2|普及+
c++·算法·二分查找·图论·洛谷
闻缺陷则喜何志丹20 天前
【二分查找】P9029 [COCI 2022/2023 #1] Čokolade|普及+
c++·算法·二分查找·洛谷
闻缺陷则喜何志丹21 天前
【二分查找】P10091 [ROIR 2022 Day 2] 分数排序|普及+
c++·算法·二分查找
不能只会打代码22 天前
力扣--1970. 你能穿过矩阵的最后一天(Java)
java·算法·leetcode·二分查找·力扣·bfs·最后可行时间