316. 去除重复字母
Difficulty: 中等
给你一个字符串 s
,请你去除字符串中重复的字母,使得每个字母只出现一次。需保证 返回结果的字典序最小(要求不能打乱其他字符的相对位置)。
注意:该题与 1081 相同
示例 1:
输入:s = "bcabc"
输出:"abc"
示例 2:
输入:s = "cbacdcbc"
输出:"acdb"
提示:
1 <= s.length <= 10<sup>4</sup>
s
由小写英文字母组成
Solution
思路:栈的使用。官方题解(视频思路很清楚)
字符串如何判断字典序大小呢,先比较字符串首字符在字典出现的次序,越早出现,字符串字典序越小,如果一样,那么就比较下一个字符的出现次序。
针对此题,因为字符长度最为(10^4),因此,最好只遍历一遍字符串,否则时间将会超时。那么遍历字符串时,先设置一个存放最终结果的list,当遍历到第i个字符时,
- step1:那么判断第i-1个字符是否在位置i之后还会出现:
- step1.1: 如果不会出现,那么就无法改变字典序,只能将第i个字符加入list中;
- step1.2:如果会出现,那么要判断在后面出现时的字典序会不会比在第i-1位置出现的字典序来得小:
- step1.2.1:如果会的话,那么要删去第i-1个字符,然后在重新回到step1;
- step1.2.2:如果不会的话,那么就保留第i-1位置的字符,之后出现的与第i-1字符相同的都不能再使用,因为他们构成的字符串字典序一定更大。
上面可能讲的有点乱,当应该可以发现存放结果的list每次增删都是在队尾操作,这很符合栈。
Language: java
class Solution {
public String removeDuplicateLetters(String s) {
char[] arr = s.toCharArray();
int[] num = new int[26];
for(char c : arr){
num[c - 'a'] ++;//记录重复的个数
}
Deque<Character> stack = new ArrayDeque<>();
int[] vis = new int[26];
for(char c : arr){
if(vis[c - 'a'] == 1) {
num[c - 'a'] --;//当前字符被访问过了,不再访问
continue;
}
if(stack.isEmpty() || stack.peek() < c){//当前字符比前一个栈顶字符来得大,符合较小字典序的规律,直接入栈
stack.push(c);
vis[c - 'a'] = 1;
num[c - 'a'] --;
}else{
while(!stack.isEmpty() && stack.peek() > c && num[stack.peek() - 'a'] > 0){//当前字符比栈顶大,同时,栈顶字符在后面字符串中还会出现,那么就出栈
vis[stack.pop() - 'a'] = 0;
}
stack.push(c);
vis[c - 'a'] = 1;
num[c - 'a'] --;
}
}
StringBuilder sb = new StringBuilder();
while(!stack.isEmpty()){
sb.append(stack.pollLast());
}
return sb.toString();
}
}