含特定字母的最小子序列
难度:
标签:
题目描述
给你一个字符串 s
,一个整数 k
,一个字母 letter
以及另一个整数 repetition
。
返回 s
中长度为 k
且 字典序最小 的子序列,该子序列同时应满足字母 letter
出现 至少 repetition
次。生成的测试用例满足 letter
在 s
中出现 至少 repetition
次。
子序列 是由原字符串删除一些(或不删除)字符且不改变剩余字符顺序得到的剩余字符串。
字符串 a
字典序比字符串 b
小的定义为:在 a
和 b
出现不同字符的第一个位置上,字符串 a
的字符在字母表中的顺序早于字符串 b
的字符。
示例 1:
输入:s = "leet", k = 3, letter = "e", repetition = 1 输出:"eet" 解释:存在 4 个长度为 3 ,且满足字母 'e' 出现至少 1 次的子序列: - "lee"("leet") - "let"("leet") - "let"("leet") - "eet"("leet") 其中字典序最小的子序列是 "eet" 。
示例 2:
输入:s = "leetcode", k = 4, letter = "e", repetition = 2 输出:"ecde" 解释:"ecde" 是长度为 4 且满足字母 "e" 出现至少 2 次的字典序最小的子序列。
示例 3:
输入:s = "bb", k = 2, letter = "b", repetition = 2 输出:"bb" 解释:"bb" 是唯一一个长度为 2 且满足字母 "b" 出现至少 2 次的子序列。
提示:
1 <= repetition <= k <= s.length <= 5 * 104
s
由小写英文字母组成letter
是一个小写英文字母,在s
中至少出现repetition
次
代码结果
运行时间: 283 ms, 内存: 17.1 MB
/*
* 思路:
* 1. 使用Java Stream处理集合操作,但由于Stream的特性,一些状态信息需要用外部变量保存。
* 2. 通过循环处理字符串,并对结果进行流操作。
* 3. 为了确保流中操作的灵活性,一些条件需要提前计算。
*/
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
import java.util.stream.Collectors;
public class SmallestSubsequenceStream {
public static String smallestSubsequence(String s, int k, char letter, int repetition) {
List<Character> stack = new ArrayList<>();
int count = 0;
int letterLeft = (int) s.chars().filter(ch -> ch == letter).count();
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
while (!stack.isEmpty() && stack.size() + (s.length() - i) > k && stack.get(stack.size() - 1) > c && (count + (stack.get(stack.size() - 1) == letter ? -1 : 0) + letterLeft >= repetition)) {
if (stack.remove(stack.size() - 1) == letter) count--;
}
if (stack.size() < k) {
if (c == letter) {
stack.add(c);
count++;
} else if (k - stack.size() > repetition - count) {
stack.add(c);
}
}
if (c == letter) letterLeft--;
}
return stack.stream()
.map(String::valueOf)
.collect(Collectors.joining());
}
}
解释
方法:
该题解采用单调栈的方法来构建字典序最小的子序列。首先,遍历字符串s,利用栈来存储潜在的最小字典序的子序列。在遍历过程中,如果当前字符c小于栈顶字符并且还有足够的空间(可以移除的字符数remains > 0)来满足最终序列长度为k,就尝试将栈顶字符弹出,直到无法弹出(栈空、当前字符不再小于栈顶字符、或者弹出后无法满足letter的repetition要求)。为了确保生成的子序列中letter出现至少repetition次,需要统计letter的总出现次数以及在栈中的数量,确保不会因为弹出操作而使得letter的数量不足。遍历完成后,如果栈中letter的数量不足,从栈尾开始替换非letter字符直到满足条件。最终,截取栈中前k个字符作为结果。
时间复杂度:
O(n)
空间复杂度:
O(n)
代码细节讲解
🦆
在题解中提到的单调栈方法中,为什么在遍历字符串时要进行栈顶字符的弹出操作,而不是直接添加所有字符到栈中?
▷🦆
题解中的算法保证了`letter`字符至少出现`repetition`次的条件是如何实现的?在弹出栈顶字符时,有哪些特殊的考虑?
▷🦆
题解中最后提到如果栈中`letter`的数量不足,会从栈尾开始替换非`letter`字符直到满足条件。在实际操作中,这种方式会不会影响到子序列的字典序最小性?
▷🦆
在题解的算法中,如何处理输入字符串`s`的长度等于所需子序列长度`k`的特殊情况?
▷