Leetcode 68 搜索插入位置 | 寻找比目标字母大的最小字母

1 题目

35. 搜索插入位置

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

请必须使用时间复杂度为 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] <= 104
  • nums无重复元素升序排列数组
  • -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 更直观)。

四、边界用例测试(均能通过)

  1. 空数组?题目约束 nums 长度 ≥ 0,但实际测试用例中 nums 非空(若为空,left=0,返回 0 也符合逻辑);
  2. 单元素数组:nums = [5],target=5 → 返回 0;target=3 → 返回 0;target=6 → 返回 1;
  3. 重复元素?题目中 nums 是升序无重复数组,无需处理重复。

3 题目

744. 寻找比目标字母大的最小字母

给你一个字符数组 letters,该数组按非递减顺序 排序,以及一个字符 targetletters至少有两个不同的字符。

返回 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=2mid=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 的字符」,属于「二分查找找上界」问题:

  1. 循环条件用 left < right(左闭右开区间 [left, right));
  2. target < letters[mid] 时,说明 mid 是候选,更新 right = mid
  3. target >= letters[mid] 时,更新 left = mid + 1
  4. 循环结束后,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 的字符」的要求:

  1. target < letters[mid]mid 是候选答案,保留(right = mid);
  2. target >= letters[mid]:目标在右侧,排除 midleft = mid + 1);
  3. 循环结束时 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:调整初始 rightletters.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'(正确)。

五、总结

你的代码核心二分逻辑是对的,仅错在:

  1. 初始 right 的取值与「越界判断」不匹配;
  2. 未处理「所有字符都 ≤ target」的边界场景。

修正后,代码可以通过所有测试用例,且逻辑简洁高效(时间复杂度 O (logn),空间复杂度 O (1))。

相关推荐
重生之我在番茄自学网安拯救世界1 小时前
网络安全中级阶段学习笔记(四):XSS-Labs 前 10 关 通关命令and实战笔记
笔记·学习·网络安全·xss·xss-labs
严文文-Chris1 小时前
【非监督学习常见算法】
学习·算法·机器学习
malajisi011 小时前
鸿蒙PC开发笔记一:HarmonyOS PC 命令行适配指南(Mac 版)
笔记·macos·harmonyos·harmony·鸿蒙pc·harmony pc
专注于大数据技术栈1 小时前
java学习--注解之@Deprecated
java·学习
CoderYanger1 小时前
动态规划算法-斐波那契数列模型:1.第N个泰波那契数
开发语言·算法·leetcode·动态规划·1024程序员节
hweiyu001 小时前
数据结构:红黑树
数据结构
红队it1 小时前
【机器学习】python旅游数据分析可视化协同过滤算法推荐系统(完整系统源码+数据库+开发笔记+详细部署教程)✅
python·mysql·算法·机器学习·数据分析·旅游
我太想进步了C~~1 小时前
Prompt Design(提示词工程)入门级了解
前端·人工智能·算法
zore_c1 小时前
【C语言】文件操作详解2(文件的顺序读写操作)
android·c语言·开发语言·数据结构·笔记·算法·缓存