[LeetCode] 704.二分查找
1. 问题描述
704. 二分查找
已解答
简单
相关标签
相关企业
给定一个 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
提示:
- 你可以假设
nums中的所有元素是不重复的。 n将在[1, 10000]之间。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 题解思路
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 = 0mid = 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换行符区分不同参数

浙公网安备 33010602011771号