本文是力扣LeeCode-316. 去除重复字母 学习与理解过程,本文仅做学习之用,对本题感兴趣的小伙伴可以出门左拐LeeCode。
给你一个字符串 s ,请你去除字符串中重复的字母,使得每个字母只出现一次。需保证 返回结果的字典序最小(要求不能打乱其他字符的相对位置)。
示例 1:
输入:s = “bcabc”
输出:“abc”
示例 2:
输入:s = “cbacdcbc”
输出:“acdb”
提示:
1 <= s.length <= 104
s 由小写英文字母组成
注意:该题与 1081 https://leetcode-cn.com/problems/smallest-subsequence-of-distinct-characters 相同
初做这道题,我一下子就想到了之前有做过 1047. 删除字符串中的所有相邻重复项 题,使用栈方法移除相同项,这道题显然也可以这样做一部分,但是由于这道题要求有三个:
笔者这边做完去重和不打乱字符出现的相对顺序后,发现对这道题的要求3束手无策了。于是参考了LeeCode博主labuladong,对这道题的题解,我才发现还可以遇山开山,遇河架桥,666。下面是,我对博主题解的简述和总结:
要求1和2可以直接用如下代码解决:
String removeDuplicateLetters(String s) {
// 存放去重的结果
Stack<Character> stack= new Stack<>();
// 布尔数组初始值为 false,记录栈中是否存在某个字符
// 输入字符均为 ASCII 字符,所以大小 256 够用了
boolean[] inStack = new boolean[256];
for (char c : s.toCharArray()) {
// 如果字符 c 存在栈中,直接跳过
if (inStack[c]) continue;
// 若不存在,则插入栈顶并标记为存在
stack.push(c);
inStack[c] = true;
}
StringBuilder sb = new StringBuilder();
while (!stack.empty()) {
sb.append(stack.pop());
}
// 栈中元素插入顺序是反的,需要 reverse 一下
return sb.reverse().toString();
}
以bcabc
举例,这个算法会返回 “bca”,已经符合要求1和要求2,但是题目希望要的答案是 “abc” 。所以此时需要考虑要求3,如何让字典项最小:以a、b、c、……举例,c的ASCII值大于b,b的ASCII值大于a,如果b比a先进栈,就需要先将b出栈pop,a才可以进栈排前面,如果c比b先进栈,就需要先将c出栈pop,b才可以进栈排前面…… 是否很像单调栈的原理
String removeDuplicateLetters(String s) {
Stack<Character> stack= new Stack<>();
boolean[] inStack = new boolean[256];
for (char c : s.toCharArray()) {
if (inStack[c]) continue;
// 插入之前,和之前的元素比较一下大小
// 如果字典序比前面的小,pop 前面的元素
while (!stack.isEmpty() && stack.peek() > c) {
// 弹出栈顶元素,并把该元素标记为不在栈中
inStack[stack.pop()] = false;
}
stack.push(c);
inStack[c] = true;
}
StringBuilder sb = new StringBuilder();
while (!stack.empty()) {
sb.append(stack.pop());
}
return sb.reverse().toString();
}
对于以上代码,我们输入 s = "bcabc"
,我们可以得出正确结果 "abc"
了,但是对于s="bcac"
,结果是ac
,结果是不对的。究其根本,在于我们一杆子打死了所有stack.peek() > c
的情况。以 s = "bcac"
为例,我们没有考虑到,将b
出栈后,后续字符串中有没有b
了,c
是没问题的,它后面还有,所以,我们需要分情况讨论:
该怎么实现这个效果呢? 计数器统计
class Solution {
public String removeDuplicateLetters(String s) {
Stack<Character> stack = new Stack<>();
boolean[] inStack = new boolean[256];
// 维护一个计数器记录字符串中字符的数量
// 因为输入为 ASCII 字符,大小 256 够
int[] count = new int[256];
for(char a : s.toCharArray()){
count[a]++;
}
for(char c : s.toCharArray()){
count[c]--; // 每遍历过一个字符,都将对应的计数减一
if(inStack[c])continue;
while(!stack.isEmpty()&&stack.peek()>c){
// 如果之后不存在栈顶元素了,则停止 pop
if(count[stack.peek()]==0)break;
// 如果之后还有,则可以大胆 pop
inStack[stack.pop()] = false;
}
stack.push(c);
inStack[c] = true;
}
StringBuilder sb = new StringBuilder();
while(!stack.isEmpty()){
sb.append(stack.pop());
}
return sb.reverse().toString();
}
}
时间复杂度: O(N)
空间复杂度: O(N)
大佬们有更好的方法,请不吝赐教,谢谢。