题目链接
题目描述
给你一个字符串 s ,请你去除字符串中重复的字母,使得每个字母只出现一次。需保证 返回结果的字典序最小(要求不能打乱其他字符的相对位置)。
示例 1:
输入:s = “bcabc”
输出:“abc”
示例 2:
输入:s = “cbacdcbc”
输出:“acdb”
提示:
- 1 < = s . l e n g t h < = 1 0 4 1 <= s.length <= 10^4 1<=s.length<=104
- s 由小写英文字母组成
解法:单调栈 & 贪心
首先我们考虑要怎么删除字符,才能使剩下的字符串字典序最小?
我们要删除第一个 s [ i ] > s [ i + 1 ] s[i] > s[i + 1] s[i]>s[i+1] 的 s [ i ] s[i] s[i],这样的话剩下的字符串字典序就最小。
我们记作这样的字符为 有效字符,我们要删除的就是这样的有效字符。
所以我们可以使用一个单调栈 s t k stk stk,从栈底 到 栈顶是递增的。假设 区间 [ 0 , i − 1 ] [0,i - 1] [0,i−1] 都已经被我们处理了,现在处理 s [ i ] s[i] s[i]。
如果 ! s t k . e m p t y ( ) & & s t k . t o p ( ) > s [ i ] !stk.empty() \&\&stk.top() > s[i] !stk.empty()&&stk.top()>s[i],说明此时的 s t k . t o p ( ) 和 s [ i ] stk.top() 和 s[i] stk.top()和s[i]就是有效字符,需要删除 s t k . t o p ( ) stk.top() stk.top(),即 s t k stk stk弹出栈顶元素。
由于题目保证每一个字符只能出现一次,所以我们需要两个哈希表 c n t 和 v i s cnt 和 vis cnt和vis, v i s vis vis 记录当前字符 s [ i ] s[i] s[i] 是否已经在栈内,如果已经在栈内,就跳过;否则才进行处理。
c n t cnt cnt 初始记录 s s s 中所有字符的出现次数。每遍历过 s [ i ] s[i] s[i], c n t [ s [ i ] ] = c n t [ s [ i ] ] − 1 cnt[s[i]] = cnt[s[i]] - 1 cnt[s[i]]=cnt[s[i]]−1。它的作用是,当 ! s t k . e m p t y ( ) & & s t k . t o p ( ) > s [ i ] !stk.empty() \&\&stk.top() > s[i] !stk.empty()&&stk.top()>s[i],栈顶元素要出栈,但是如果 剩下的未遍历的区间已经不存在字符 s t k . t o p ( ) stk.top() stk.top(),也就是判断 c n t [ s t k . t o p ( ) ] cnt[stk.top()] cnt[stk.top()] 是否大于0。如果不存在了,就不能出栈;否则可以。
时间复杂度: O ( n ) O(n) O(n)
C++代码:
class Solution {
public:
string removeDuplicateLetters(string s) {
unordered_map<char,int> cnt,vis;
for(auto c:s) cnt[c]++;
string stk;
for(auto ch:s){
if(!vis[ch]){
while(!stk.empty() && stk.back() > ch){
if(cnt[stk.back()]){
vis[stk.back()] = 0;
stk.pop_back();
}
else break;
}
stk.push_back(ch);
vis[ch] = 1;
}
cnt[ch]--;
}
return stk;
}
};