1 题目
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n) 的算法。
示例 1:
输入: nums = [1,3,5,6], target = 5
输出: 2
示例 2:
输入: nums = [1,3,5,6], target = 2
输出: 1
示例 3:
输入: nums = [1,3,5,6], target = 7
输出: 4
提示:
1 <= nums.length <= 104-104 <= nums[i] <= 104nums为 无重复元素 的 升序排列数组-104 <= target <= 104
2 代码实现
cpp
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int left = 0 ;
int right = nums.size() - 1 ;
while(left <= right){
int mid = left + (right - left) / 2 ;
if (target == nums[mid]){
return mid ;
}else if (target > nums[mid]){
left = mid + 1;
}else if (target < nums[mid]){
right = mid - 1;
}
}
return left;
}
};
一、代码正确性验证
1. 核心逻辑匹配题意
题目要求:
- 找到目标值 → 返回其下标;
- 没找到 → 返回「按顺序插入的位置」。
你的代码逻辑:
- 闭区间
[left, right]二分查找,找到target直接返回mid; - 循环结束时
left > right,此时left恰好是插入位置(关键结论)。
2. 为什么循环结束返回 left 是对的?
循环终止条件是 left > right,此时:
- 所有小于
target的元素都在left左侧; - 所有大于
target的元素都在left右侧; - 例如:
- nums = [1,3,5,6], target = 5 → 找到,返回 2;
- nums = [1,3,5,6], target = 2 → 循环结束时 left=1,right=0 → 返回 1(正确插入位置);
- nums = [1,3,5,6], target = 7 → 循环结束时 left=4,right=3 → 返回 4(插入末尾);
- nums = [1,3,5,6], target = 0 → 循环结束时 left=0,right=-1 → 返回 0(插入开头)。
二、代码优化(非必需,但更简洁)
你的代码已经满足 AC 要求,仅做可读性优化:
cpp
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int left = 0, right = nums.size() - 1;
while (left <= right) {
int mid = left + (right - left) / 2; // 避免溢出
if (nums[mid] == target) {
return mid; // 找到目标,直接返回
} else if (nums[mid] < target) {
left = mid + 1; // 目标在右侧,收缩左边界
} else {
right = mid - 1; // 目标在左侧,收缩右边界
}
}
return left; // 未找到,返回插入位置
}
};
三、关键结论(为什么返回 left 而不是 right)
循环结束时 left = right + 1,举例说明:
| 场景 | 循环结束后 left | right | 插入位置 |
|---|---|---|---|
| target 比所有元素小 | 0 | -1 | 0 |
| target 比所有元素大 | nums.size() | nums.size()-1 | nums.size() |
| target 在两元素之间 | 插入位置 | 插入位置 - 1 | left |
因此返回 left 是唯一正确的选择(返回 right + 1 也等价,但 left 更直观)。
四、边界用例测试(均能通过)
- 空数组?题目约束
nums长度 ≥ 0,但实际测试用例中nums非空(若为空,left=0,返回 0 也符合逻辑); - 单元素数组:nums = [5],target=5 → 返回 0;target=3 → 返回 0;target=6 → 返回 1;
- 重复元素?题目中 nums 是升序无重复数组,无需处理重复。
3 题目
给你一个字符数组 letters,该数组按非递减顺序 排序,以及一个字符 target。letters 里至少有两个不同的字符。
返回 letters 中大于 target 的最小的字符。如果不存在这样的字符,则返回 letters 的第一个字符。
示例 1:
输入: letters = ['c', 'f', 'j'],target = 'a'
输出: 'c'
解释:letters 中字典上比 'a' 大的最小字符是 'c'。
示例 2:
输入: letters = ['c','f','j'], target = 'c'
输出: 'f'
解释:letters 中字典顺序上大于 'c' 的最小字符是 'f'。
示例 3:
输入: letters = ['x','x','y','y'], target = 'z'
输出: 'x'
解释:letters 中没有一个字符在字典上大于 'z',所以我们返回 letters[0]。
4 代码实现
cpp
class Solution {
public:
char nextGreatestLetter(vector<char>& letters, char target) {
int left = 0 ;
int right = letters.size() ;
while (left < right ){
int mid = left + (right - left) / 2 ;
if (target < letters[mid]){
right = mid ;
}else if (target >= letters[mid]){
left = mid + 1 ;
}
}
if (left == letters.size()){
return letters[0];
}else {
return letters[left];
}
}
};
第一次提交的错误代码
cpp
class Solution {
public:
char nextGreatestLetter(vector<char>& letters, char target) {
int left = 0 ;
int right = letters.size() - 1 ;
while (left <= right ){
int mid = left + (right - left) / 2 ;
if (target == letters[mid]){
return letters[mid - 1];
}else if (target < letters[mid]){
right = mid - 1;
}else if (target > letters[mid]){
left = mid + 1 ;
}
}
return letters[0];
}
};
一、核心错误 1:找到目标值时返回 letters[mid - 1](完全违背题意)
题目要求返回「大于 target 的最小字符」,但你在 target == letters[mid] 时直接返回 letters[mid - 1]:
- 示例 2 中
letters = ['c','f','j'], target = 'c',mid会命中'c'(下标 0),此时mid-1 = -1→ 数组越界; - 即使不越界(比如
letters = ['c','c','f'], target='c'),mid-1也是'c',依然不满足「大于 target」的要求。
正确逻辑 :找到 target == letters[mid] 时,不能直接返回,因为数组可能有重复字符,且需要找「比 target 大的最小字符」,需继续在右侧查找(比如 letters = ['c','c','f'],target='c' 时应返回 'f')。
二、核心错误 2:循环结束后直接返回 letters[0](覆盖了有效结果)
循环终止条件是 left > right,此时 left 可能指向「大于 target 的最小字符」,而非一定没有结果:
- 示例 1 中
letters = ['c','f','j'], target = 'a':循环过程:left=0, right=2→mid=1('f' > 'a')→right=0;再循环:mid=0('c' > 'a')→right=-1;循环结束,此时left=0(指向 'c',是正确答案),但你的代码直接返回letters[0]看似巧合对了,实则逻辑错误; - 示例 2 中循环结束后
left=1(指向 'f'),但你的代码不会走到这一步(因为提前错误返回); - 示例 3 中
letters = ['x','x','y','y'], target = 'z':循环结束后left=4(超出数组长度),此时才需要返回letters[0]。
三、核心错误 3:区间更新逻辑错误(导致漏查右侧更小的符合条件的字符)
当 target < letters[mid] 时,你更新 right = mid - 1,会直接排除 mid 这个可能的答案:
- 比如
letters = ['c','f','j'], target='c',mid=1('f' > 'c'),此时right=0,后续循环只会查[0,0]('c'),漏查了 'f' 这个正确答案; - 正确逻辑:
target < letters[mid]时,mid是候选答案,需保留,因此应更新right = mid - 1→ 改为right = mid(左闭右开区间),或调整循环逻辑。
四、错误总结(对比正确逻辑)
| 你的错误逻辑 | 正确逻辑 |
|---|---|
| 找到 target 时返回 mid-1 | 找到 target 时,继续向右查找(因为需要「大于」target 的最小字符) |
| target < letters[mid] → right=mid-1 | target <letters [mid] 时,mid 是候选,需保留,right=mid(左闭右开)或不立即排除 mid |
| 循环结束直接返回 letters [0] | 循环结束后,先判断 left 是否越界:越界则返回 letters [0],否则返回 letters [left] |
关键纠正思路
这道题的核心是「找第一个大于 target 的字符」,属于「二分查找找上界」问题:
- 循环条件用
left < right(左闭右开区间[left, right)); target < letters[mid]时,说明 mid 是候选,更新right = mid;target >= letters[mid]时,更新left = mid + 1;- 循环结束后,
left若等于数组长度(无符合条件字符),返回 letters [0],否则返回 letters [left]。
你的代码本质是把**「找大于 target 的最小字符」当成了「找等于 target 的字符」,且区间更新和返回逻辑完全错误,导致无法通过用例**。
第二次写错的代码
cpp
class Solution {
public:
char nextGreatestLetter(vector<char>& letters, char target) {
int left = 0 ;
int right = letters.size() - 1 ;
while (left < right ){
int mid = left + (right - left) / 2 ;
if (target < letters[mid]){
right = mid ;
}else if (target >= letters[mid]){
left = mid + 1 ;
}
}
if (left == letters.size()){
return letters[0];
}else {
return letters[left];
}
}
};
你的代码已经接近正确,仅存在一个关键边界问题,我们先分析代码的核心逻辑,再指出问题并修正:
一、代码核心逻辑(正确部分)
你选择了「左闭右开区间」的二分风格(left < right 循环条件),核心思路符合「找第一个大于 target 的字符」的要求:
target < letters[mid]:mid是候选答案,保留(right = mid);target >= letters[mid]:目标在右侧,排除mid(left = mid + 1);- 循环结束时
left == right,指向候选位置。
二、唯一错误:left == letters.size() 的判断永远不成立
因为初始 right = letters.size() - 1,且循环条件是 left < right,所以 left 的最大值只能是 letters.size() - 1(不可能等于 letters.size())。
这个错误会导致:当所有字符都 ≤ target 时(比如示例 3:letters = ['x','x','y','y'], target = 'z'),代码会错误返回 letters[left](即 letters[3] = 'y'),而非题目要求的 letters[0]。
三、修正方案(两种方式均可)
方式 1:调整初始 right 为 letters.size()(标准左闭右开)
cpp
class Solution {
public:
char nextGreatestLetter(vector<char>& letters, char target) {
int left = 0 ;
int right = letters.size() ; // 修正:初始right为数组长度(左闭右开)
while (left < right ){
int mid = left + (right - left) / 2 ;
if (target < letters[mid]){
right = mid ;
}else { // 合并target >= letters[mid],简化逻辑
left = mid + 1 ;
}
}
// 循环结束后,left可能等于letters.size()(无符合条件字符)
return left == letters.size() ? letters[0] : letters[left];
}
};
方式 2:保留初始 right = size()-1,增加「候选字符是否大于 target」的判断
cpp
class Solution {
public:
char nextGreatestLetter(vector<char>& letters, char target) {
int left = 0 ;
int right = letters.size() - 1 ;
while (left < right ){
int mid = left + (right - left) / 2 ;
if (target < letters[mid]){
right = mid ;
}else {
left = mid + 1 ;
}
}
// 检查最终left指向的字符是否大于target,否则返回第一个
return letters[left] > target ? letters[left] : letters[0];
}
};
四、测试用例验证(以示例 3 为例)
示例 3:letters = ['x','x','y','y'], target = 'z'
- 方式 1:初始
right=4,循环结束后left=4→ 返回letters[0] = 'x'(正确); - 方式 2:循环结束后
left=3(指向 'y'),'y' > 'z'为 false → 返回letters[0] = 'x'(正确)。
五、总结
你的代码核心二分逻辑是对的,仅错在:
- 初始
right的取值与「越界判断」不匹配; - 未处理「所有字符都 ≤ target」的边界场景。
修正后,代码可以通过所有测试用例,且逻辑简洁高效(时间复杂度 O (logn),空间复杂度 O (1))。