目录

题目描述

704. 二分查找

image-20230304215758866

题意分析

在一个升序排列、元素互不相同的整数数组里找 target,找到返回它的下标,找不到返回 -1。题目还明确要求算法的时间复杂度是 $O(\log n)$,这句话直接把「从头扫到尾」排除掉了。

「有序」是全题唯一的算法前提,也是唯一可以利用的结构。 有序意味着比较任意一个位置 mid 上的值与 target,都能得到一个覆盖半个数组的结论:若 nums[mid] < target,那么由升序可知 mid 及其左边的所有元素都小于 target,它们全都不可能是答案;若 nums[mid] > target,同理 mid 及其右边全部作废。换句话说,一次比较的信息量是「排除一半」,而不是「排除一个」——这正是对数复杂度的来源。这里真正被用到的性质是单调性:判定式「nums[i] < target」沿着下标从左到右只会从真变假、不会来回摇摆,因此存在一个明确的分界点。任何丢掉单调性的输入(哪怕只是把两个元素调换位置)都会让这套推理立即失效。

「元素互不相同」这个条件让问题变得简单:命中即答案,不需要考虑「返回哪一个」。一旦允许重复,命中之后还得继续向左或向右收缩才能定位首末位置,那已经是另一类问题了。

需要留意的边界情形:数组只有一个元素(最容易暴露「少检查一次」的收尾错误);target 比所有元素都小或都大;target 恰好落在两个相邻元素之间。后两种都属于「不存在」的情形,必须稳定地返回 -1,不能返回其他编码值。

解法:闭区间二分查找

核心思路

闭区间 [left, right] 表示仍可能包含 target 的范围。比较中点后,利用数组升序性排除中点及一侧;区间为空仍未命中时返回 -1

解题步骤

  1. 初始化 left = 0right = nums.length - 1
  2. left <= right 时计算中点 mid
  3. 命中就返回 mid;目标较大则令 left = mid + 1,否则令 right = mid - 1
  4. 循环结束说明目标不存在,返回 -1

代码实现

class Solution {
    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;
            }
            if (nums[mid] < target) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        return -1;
    }
}
func search(nums []int, target int) int {
    left, right := 0, len(nums)-1
    for left <= right {
        mid := left + (right-left)/2
        if nums[mid] == target {
            return mid
        }
        if nums[mid] < target {
            left = mid + 1
        } else {
            right = mid - 1
        }
    }
    return -1
}

复杂度分析

  • 时间复杂度:$O(\log n)$,每轮排除约一半候选。
  • 空间复杂度:$O(1)$。

关键点总结

  • 闭区间写法必须配套使用 right = n - 1left <= rightmid ± 1
  • 中点写成 left + (right - left) / 2,避免加法溢出。
  • 每次更新都排除已经检查过的 mid,保证区间持续缩小。

易错点总结

  • 把循环条件写成 left < right,会漏查最后一个位置。
  • 把右边界初始化为数组长度,会访问越界。
  • 更新为 left = midright = mid,可能导致死循环。
  • 未找到时应返回 -1,不要误返回插入位置 left

相似题目

题目 难度 考察点
34. 在排序数组中查找元素的第一个和最后一个位置 中等 元素可重复,命中后不能立刻返回,要把「找到即停」改成两次「找边界」的二分
35. 搜索插入位置 简单 未命中时要求返回插入位置,答案正好是本题循环退出后的 left,收尾不同而循环体完全一致
278. 第一个错误的版本 简单 没有数组可读,单调性来自「一旦是坏版本后面全坏」的判定函数,还要额外优化调用次数
367. 有效的完全平方数 简单 二分的对象是候选答案而不是数组下标,判定要算平方并防止溢出,只需回答存在与否
540. 有序数组中的单一元素 中等 没有 target 可比,单调量是「配对是否错位」,mid 需要先对齐到偶数下标才能比较
LCR 068. 搜索插入位置 简单 与 35 同题换号,适合专门练「插入位置 = 小于 target 的元素个数」这个等价说法
LCR 070. 有序数组中的单一元素 中等 与 540 同题换号,重点在于说明为什么线性异或不满足对数复杂度要求
剑指 Offer 53 - I. 在排序数组中查找数字 I 简单 要的是出现次数而非下标,需要左右边界各二分一次再相减,比本题多一层边界推导
剑指 Offer 53 - II. 0~n-1中缺失的数字 简单 判定式换成 nums[mid] == mid,利用的是下标与值的对应关系而不是与外部目标比较
面试题 10.05. 稀疏数组搜索 简单 数组中夹有空字符串,mid 处可能无法比较,必须先线性挪开空串,最坏复杂度因此退化成线性