LeetCode 744. 寻找比目标字母大的最小字母 | 从低效到最优的二分解法优化

在算法刷题中,二分查找是高频考点,而「寻找比目标字母大的最小字母」这道题(LeetCode 744)是二分查找的经典应用场景。本文将从初版低效解法 入手,分析问题所在,逐步优化到最优解法,帮你理解二分查找的核心逻辑和题目特性的利用技巧。

一、题目描述

原题链接

LeetCode 744. Find Smallest Letter Greater Than Target(https://leetcode.cn/problems/find-smallest-letter-greater-than-target/

核心要求

给你一个字符数组 letters,该数组按非递减顺序 排列,以及一个字符 target。请你找出并返回 letters 中大于 target 的最小字符。如果不存在这样的字符,则返回 letters 的第一个字符。

示例

  • 示例 1:输入: letters = ["c","f","j"], target = "a",输出: "c"
  • 示例 2:输入: letters = ["c","f","j"], target = "c",输出: "f"
  • 示例 3:输入: letters = ["c","f","j"], target = "j",输出: "c"

优化解法:最优二分实现

1. 优化思路

  • 利用 letters 非递减的特性,去掉冗余去重,直接操作原数组,节省时间和空间;
  • 修正二分逻辑,精准瞄准「大于 target 的最小字符」;
  • 提前处理边界场景(target 大于所有字符),避免数组越界。
  1. 最优代码
java 复制代码
public char nextGreatestLetter(char[] letters, char target) {
    int left = 0;
    int right = letters.length - 1;
    
    // 提前处理边界:target大于/等于所有字符,直接返回第一个元素
    if (letters[right] <= target) {
        return letters[left];
    }
    
    // 二分查找:找大于target的最小字符
    while (left <= right) {
        // 防止left+right溢出,等价于(left+right)/2
        int mid = left + (right - left) / 2;
        if (letters[mid] > target) {
            // 当前元素大于target,尝试找更小的(左移右边界)
            right = mid - 1;
        } else {
            // 当前元素<=target,需要右移左边界,找更大的元素
            left = mid + 1;
        }
    }
    
    // 循环结束后,left指向第一个大于target的字符
    return letters[left];
}

核心逻辑解析

(1)边界预处理

if (letters[right] <= target) 直接判断「target 是否大于等于最后一个字符」,若是则返回第一个字符,避免后续二分越界。

(2)二分查找核心
  • letters[mid] > target:说明 mid 位置的字符是候选,但可能有更小的符合条件的字符,因此左移右边界(right = mid - 1);
  • letters[mid] <= target:说明 mid 位置的字符不符合要求,需要找更大的,因此右移左边界(left = mid + 1);
  • 循环结束时,left 必然指向「大于 target 的最小字符」(因为预处理已排除所有字符都 <=target 的情况)。
(3)时间 / 空间复杂度
  • 时间复杂度:O (logn)(纯二分查找,无额外遍历);
  • 空间复杂度:O (1)(仅用了几个变量,无额外数据结构)。

四、测试用例验证

用例 1:常规场景(target 小于所有字符)

输入:letters = ['c','f','j'], target = 'a'执行流程:

  • 边界判断:letters[2]='j' > 'a',进入二分;
  • 第一次循环:mid=1letters[1]='f'>'a'right=0
  • 第二次循环:mid=0letters[0]='c'>'a'right=-1
  • 循环结束,返回 letters[0]='c'(正确)。

用例 2:target 等于中间字符

输入:letters = ['c','f','j'], target = 'c'执行流程:

  • 边界判断:letters[2]='j' > 'c',进入二分;
  • 第一次循环:mid=1letters[1]='f'>'c'right=0
  • 第二次循环:mid=0letters[0]='c'<=cleft=1
  • 循环结束,返回 letters[1]='f'(正确)。

用例 3:边界场景(target 等于最后一个字符)

输入:letters = ['c','f','j'], target = 'j'执行流程:

  • 边界判断:letters[2]='j' <= 'j' → 直接返回 letters[0]='c'(正确)。

用例 4:含重复元素场景

输入:letters = ['c','c','f','j'], target = 'c'执行流程:

  • 边界判断:letters[3]='j' > 'c',进入二分;
  • 第一次循环:mid=1letters[1]='c'<=cleft=2
  • 第二次循环:mid=2letters[2]='f'>'c'right=1
  • 循环结束,返回 letters[2]='f'(正确)。

五、总结

  1. 二分查找的核心是精准匹配题目要求的判断条件,本题需严格区分「>=target」和「>target」,否则会导致结果错误;
  2. 解题时要充分利用题目给出的特性(如本题「非递减排列」),去掉冗余逻辑(如去重),提升效率;
  3. 边界条件是二分查找的高频坑点,提前预处理边界场景(如 target 大于所有字符),能有效避免数组越界。
相关推荐
一条大祥脚18 小时前
26.1.3 快速幂+容斥 树上dp+快速幂 带前缀和的快速幂 正序转倒序 子序列自动机 线段树维护滑窗
数据结构·算法
二狗哈18 小时前
czsc入门5: Tick RawBar(原始k线) NewBar (新K线)
算法·czsc
Tisfy18 小时前
LeetCode 0865.具有所有最深节点的最小子树:深度优先搜索(一次DFS + Python5行)
算法·leetcode·深度优先·dfs·题解
Q741_14718 小时前
C++ 队列 宽度优先搜索 BFS 力扣 429. N 叉树的层序遍历 C++ 每日一题
c++·算法·leetcode·bfs·宽度优先
Yzzz-F18 小时前
P4145 上帝造题的七分钟 2 / 花神游历各国[线段树 区间开方(剪枝) + 区间求和]
算法·机器学习·剪枝
Zzz不能停18 小时前
堆排序算法及大小堆区别
数据结构·算法
zd84510150018 小时前
stm32f407 电机多轴联动算法
stm32·单片机·算法
代码游侠19 小时前
应用——Linux FrameBuffer图形显示与多线程消息系统项目
linux·运维·服务器·开发语言·前端·算法
Eloudy19 小时前
矩阵张量积(Kronecker积)的代数性质与定理
算法·量子计算