- 题目解析
该题"讲人话"就是:给定一个升序、无重复的整数数组nums和一个目标值target,找到target在数组中的下标;如果找不到,就返回-1。核心要求是必须用O(log n)的时间复杂度,不能用暴力遍历。
举个简单例子:nums = [-1,0,3,5,9,12],target = 9,那么返回4(9在数组中的下标);如果target = 2,数组中没有,就返回-1。
题目难度:Easy(简单),核心考察:二分查找的核心思想、区间定义与边界处理,是算法入门的必刷题。
- 解法详解
本题有两种经典解法,均基于二分查找思想,核心差异在于"区间定义",从基础到进阶,新手可循序渐进理解~
解法1(闭区间 [left, right],新手首选)
思路最直观:将搜索范围定义为闭区间[left, right],每次计算中间位置mid,通过比较nums[mid]和target的大小,缩小搜索范围,直到找到目标或区间为空。
具体步骤:
-
初始化left = 0(数组起始下标),right = nums.size() - 1(数组末尾下标),覆盖整个数组。
-
循环条件:left ≤ right(闭区间中,left == right时仍有一个元素,需要检查)。
-
计算mid:mid = left + (right - left) / 2(避免left+right溢出,等价于(left+right)/2)。
-
比较判断:若nums[mid] == target,直接返回mid;若nums[mid] < target,目标在右半区间,left = mid + 1;若nums[mid] > target,目标在左半区间,right = mid - 1。
-
循环结束后仍未找到,返回-1。
完整代码(C++,可直接提交):
cpp
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0, right = nums.size() - 1; // 闭区间[left, right]初始化
while (left <= right) { // 闭区间有效条件:left ≤ right
// 计算mid,避免left+right整数溢出
int mid = left + (right - left) / 2;
if (nums[mid] < target) {
left = mid + 1; // 目标在右半区间,左边界右移
} else if (nums[mid] > target) {
right = mid - 1; // 目标在左半区间,右边界左移
} else {
return mid; // 找到目标,返回下标
}
}
return -1; // 区间为空,未找到目标
}
};
注意:这种写法是新手最容易掌握的,逻辑清晰,边界处理简单。核心是记住"闭区间"的规则:left和right都包含在搜索范围内,更新边界时要跳过mid(因为mid已判断不是目标)。
解法2(左闭右开区间 [left, right),进阶写法)
核心优化:将搜索范围定义为左闭右开区间[left, right),即left包含在区间内,right不包含。这种写法更贴合STL中lower_bound、upper_bound的底层实现,边界处理更统一。
核心思路:
- 初始化left = 0,right = nums.size()(右边界为数组长度,不包含最后一个元素)。
- 循环条件:left < right(左闭右开区间中,left == right时区间为空)。
- 计算mid的方式不变,边界更新差异:nums[mid] > target时,right = mid(因为right不包含,直接缩小到mid即可);其他情况和闭区间一致。
完整代码(C++,可直接提交):
```cpp
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0, right = nums.size(); // 左闭右开区间[left, right)初始化
while (left < right) { // 左闭右开区间有效条件:left < right
int mid = left + (right - left) / 2;
if (nums[mid] < target) {
left = mid + 1; // 目标在右半区间,左边界右移
} else if (nums[mid] > target) {
right = mid; // 右边界不包含,直接设为mid
} else {
return mid; // 找到目标,返回下标
}
}
return -1; // 区间为空,未找到目标
}
};
- 关键知识点总结
-
二分查找核心:利用数组有序特性,每次将搜索范围减半,时间复杂度从暴力遍历的O(n)优化到O(log n),适用于有序、无重复的数组查找。
-
区间定义是核心:两种写法的关键区别的是"right的初始化"和"循环条件",记住"闭区间含right,开区间不含right"即可。
-
防溢出技巧:mid = left + (right - left) / 2 是必学技巧,避免left和right都接近int最大值时,left+right溢出。
- 常见错误思路提醒
很多新手刚学二分查找,容易踩3个坑,一定要注意:
-
坑1:循环条件写错------闭区间用left <= right,若写成left < right,会漏掉left == right时的元素,导致漏查。
-
坑2:mid计算溢出------直接写mid = (left + right) / 2,在大数组中会溢出,必须用left + (right - left) / 2。
-
坑3:边界更新错误------闭区间中,nums[mid] > target时,误将right = mid - 1写成right = mid,会导致死循环;左闭右开区间中,误将right = mid写成right = mid - 1,会漏掉元素。
- 后续练习建议
二分查找是高频考点,基础题掌握后,可练习以下变种题,巩固区间思维:
-
LeetCode 35. 搜索插入位置(二分查找的延伸,找不到目标时返回插入位置)
-
LeetCode 153. 寻找旋转排序数组中的最小值(旋转数组的二分查找,进阶)
-
LeetCode 74. 搜索二维矩阵(二维有序数组的二分查找,拓展应用)
每日一题,坚持打卡,慢慢积累算法思维~ 有问题欢迎在评论区留言讨论!