[LeetCode] 704.二分查找

1. 问题描述


704. 二分查找

已解答

简单

相关标签

premium lock icon

相关企业

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target  ,写一个函数搜索 nums 中的 target,如果 target 存在返回下标,否则返回 -1。

你必须编写一个具有 O(log n) 时间复杂度的算法。

示例 1:

输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4

示例 2:

输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1

提示:

  1. 你可以假设 nums 中的所有元素是不重复的。
  2. n 将在 [1, 10000]之间。
  3. nums 的每个元素都将在 [-9999, 9999]之间。

2. 实现思路


2.1 自己的思路


无论实现与否,都是群星的一丝痕迹~~(唔,所以一定要写出来啊喂)~

好吧,果然一年没碰算法了,复健练习都感觉有些吃力

二分应该是挺基础的了

  • 第一次提交:

突然忘了二分思想是啥了,问了下AI大概了解是不断二分取中点,然后在AI即将生成代码示例时马上关掉)

当时写到while这里就在思考,里面条件咋写?边界是什么?想不清楚就临时写了个中点值不为target,当时其实就有点疑惑为什么right要置为mid-1而不是mid呢,left也同理

public class Solution {
    public int Search(int[] nums, int target) {
        int left = 0;
        int right = nums.Length - 1;
        int mid = left + (right - left) / 2;
        while (nums[mid] != target)
        {
            if (nums[mid] > target)
                // target在左边
                right = mid - 1;
            else
                // target在右边
                left = mid + 1;
            int newMid = left + (right - left) / 2;
            if (mid == newMid) return -1;
            mid = newMid;
        }
        return mid;
    }
}

在遇到测试用例[-1,0,3,5,9,12]\n13时就爆出IndexOutOfRange数组越界了…打个断点发现,该用例运行过程中,left会不断趋向right,直到最后一次循环是,left=right=5,走到else处left等于6,newmid也就是6导致了越界

  • 第二次提交:
public class Solution
{
    public int Search(int[] nums, int target)
    {
        int left = 0;
        int right = nums.Length - 1;
        int mid = left + (right - left) / 2;
        while (nums[mid] != target)
        {
            if (nums[mid] > target)
                // target在左边
                right = mid;
            else
                // target在右边
                left = mid;
            int newMid = left + (right - left) / 2;
            if (mid == newMid) return -1;
            mid = newMid;
        }
        return mid;
    }
}

这次是解答错误,遇到测试用例[2,5]\n5时,应该返回1,实际返回-1,这里就在想第一次提交中是不是+1-1导致的越界呢,但是改了后这里newMid在第一次循环时,newMid = 0 + (1 - 0) / 2,还是等于0,于是错误地走到了return -1处。

无奈再回去看看最初问AI的二分思路是不是漏了什么,才发现末尾边界条件是left > right时区间无效,目标值不存在

  • 最后的正确提交:
public class Solution
{
    public int Search(int[] nums, int target)
    {
        int left = 0;
        int right = nums.Length - 1;
        int mid;
        do
        {
            mid = left + (right - left) / 2;
            if (nums[mid] == target) return mid;
            else if (nums[mid] > target) right = mid - 1;
            else if (nums[mid] < target) left = mid + 1;
        } while (left <= right);
        return -1;
    }
}

2.2 题解思路


704. 二分查找 - 力扣(LeetCode)

2.3 优化(或者敲一遍题解?)


优化方面,我最后的do-while有一个隐藏边界,如果数组为空,会直接报错,还是改成while的写法更合适,这样在空数组下也能正确返回预期值-1

再敲一遍~可以直观感受到思路清晰了许多,代码结构看着也更优雅了

/*
 * @lc app=leetcode.cn id=704 lang=csharp
 *
 * [704] 二分查找
 */

var s = new Solution();
s.Search([2, 5], 5);

// @lc code=start
public 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;
            int midValue = nums[mid];
            if (midValue == target) return mid;
            else if (midValue > target) right = mid - 1;
            else if (midValue < target) left = mid + 1;
        }
        return -1;
    }
}
// @lc code=end

3. 补充知识と杂谈,总结


总结下来就是,切记二分查找的结束条件是left > right。也就是二分查找的条件是查找范围不为空,即 left≤right

时间复杂度由于每次查找范围都会缩小一半,为O(log n),空间复杂度为O(1)

为什么left要置于mid + 1,right要置于mid - 1呢?

这是一个非常关键的问题,直接关系到二分查找会不会死循环以及能不能覆盖所有元素。

把 left 设为 mid + 1、right 设为 mid - 1,根本原因在于我们定义的搜索区间是“左闭右闭” [left, right],而 mid 已经被检查过且确定不是答案了,所以下一轮必须把它剔除出去。

1. 核心逻辑:剔除无效的 mid

既然进入了 else if (nums[mid] < target) 分支,说明 nums[mid] 严格小于目标值。

  • 因为数组是有序的(升序),那么 mid 位置及其左边所有元素([left ... mid])全都小于 target,绝对不可能是答案。
  • 所以下一轮的搜索范围必须从 mid + 1 开始,才能把这些无效数据全部丢掉。

同理,当 nums[mid] > target 时,mid 及其右边所有元素([mid ... right])全都大于目标值,所以下一轮右边界必须缩到 mid - 1。

一句话总结:mid 已经考了零分,下一轮阅卷就没必要再看它了。


2. 如果写成 left = mid 或 right = mid 会发生什么?(死循环)

这是面试中最容易掉进去的坑。假设数组只有两个元素:nums = [1, 2],我们要找 target = 2。

  • 初始:left = 0, right = 1
  • 计算:mid = 0 + (1 - 0) / 2 = 0
  • 比较:nums[0] = 1 < 2,所以进入 left 更新。

错误写法(left = mid):

  • 更新后:left = 0, right = 1
  • 你会发现 left 和 right 完全没有变化!程序陷入了 mid 永远等于 0 的死循环,直到超时崩溃。

正确写法(left = mid + 1):

  • 更新后:left = 1, right = 1
  • 下一轮:mid = 1,找到 2,成功返回。

核心定律:在“左闭右闭”区间中,必须使用 mid + 1 和 mid - 1,否则当区间缩小到 [left, left+1] 时,计算出的 mid 始终等于 left,会引发死循环。


3. 对比另一种区间写法(左闭右开 [left, right))

如果你看过其他二分查找模板,可能会发现有人写 while (left < right),且更新方式是 left = mid + 1 但 right = mid。

这是因为区间定义变了:

  • 如果定义右边界是开区间(不包含 right),那么当 nums[mid] > target 时,新区间变成 [left, mid),因为 mid 虽然不符合,但作为开区间的边界,它不会被包含进来。

映射到你现在的模板:
你的模板是 while (left <= right)(左闭右闭),所以必须配套 mid ± 1。

只要记住:你检查过 mid,它就永远退出了下一次的搜索范围,至于用 +1/-1 还是只用 +1,取决于你的右边界是闭还是开。


4. 极端边界验证(防越界)

假设数组 nums = [1],target = 5:

  • left = 0, right = 0
  • mid = 0,nums[0] < 5,执行 left = mid + 1 → left = 1。
  • 此时 left = 1, right = 0,left > right,循环结束,返回 1。

如果写的是 left = mid(即 left = 0),那么 left 永远无法大于 right,循环死锁。

总结一句话:mid + 1 和 mid - 1 是为了将搜索区间精准地收缩到 mid 的左右两侧,同时确保程序在只剩两个元素时能正确推进,避免死循环。这正是二分查找“每次排除一半”的精髓所在。

另外是关于插件使用的小知识,原来vscode leetcode插件是可以用本地测试用例文件的,写法如下

[-1,0,3,5,9,12]\n9
[-1,0,3,5,9,12]\n2
[-1,0,3,5,9,12]\n13
[2,5]\n5

一行对应一个测试用例,用\n换行符区分不同参数

posted @ 2026-07-31 10:59  绘星tsuki  阅读(15)  评论(0)    收藏  举报