在算法刷题中,二分查找是高频考点,而「寻找比目标字母大的最小字母」这道题(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 大于所有字符),避免数组越界。
- 最优代码
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=1,letters[1]='f'>'a'→right=0; - 第二次循环:
mid=0,letters[0]='c'>'a'→right=-1; - 循环结束,返回
letters[0]='c'(正确)。
用例 2:target 等于中间字符
输入:letters = ['c','f','j'], target = 'c'执行流程:
- 边界判断:
letters[2]='j' > 'c',进入二分; - 第一次循环:
mid=1,letters[1]='f'>'c'→right=0; - 第二次循环:
mid=0,letters[0]='c'<=c→left=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=1,letters[1]='c'<=c→left=2; - 第二次循环:
mid=2,letters[2]='f'>'c'→right=1; - 循环结束,返回
letters[2]='f'(正确)。
五、总结
- 二分查找的核心是精准匹配题目要求的判断条件,本题需严格区分「>=target」和「>target」,否则会导致结果错误;
- 解题时要充分利用题目给出的特性(如本题「非递减排列」),去掉冗余逻辑(如去重),提升效率;
- 边界条件是二分查找的高频坑点,提前预处理边界场景(如 target 大于所有字符),能有效避免数组越界。