316.去除重复字母

【LetMeFly】316.去除重复字母:单调栈

力扣题目链接:https://leetcode.cn/problems/remove-duplicate-letters/

给你一个字符串 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 相同

方法一:单调栈

这道题需要满足三个要求:

  1. 去重
  2. 按原始相对顺序
  3. 字典序最小

我们先来用栈实现一下“1.”和“2.”。至于为什么用栈,到后面就知道了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
string removeDuplicateLetters(string& s) {
bool isInStack[26] = {false}; // isInStack[i]:字母'a'+i是否已经在栈中

stack<char> st;
for (char& c : s) { // 遍历字符串
if (isInStack[c - 'a']) { // 因为要去重,所以时刻保证一种字母在栈中只出现一次
continue;
}
st.push(c);
isInStack[c - 'a'] = true;
}

string ans;
while (st.size()) { // 将栈中元素全部加入字符串中
ans += st.top();
st.pop();
}
reverse(ans.begin(), ans.end()); // 由于栈的特性,故需要反转一下字符串,以保证相对顺序不变
return ans;
}

上述代码中,我们用布尔类型的isInStack来记录某个元素是否已经位于了栈中。遍历字符串时,如果某个元素还不在栈中,就将这个元素入栈,同时更新isInStack

如何实现字典序最小呢?很容易想到,越小的字母尽量地越靠前,就能使字典序尽可能地小。

因此,在遍历到一个不在栈中的字母时,如果前面有比它大的字母,并且前面这个字母以后还会出现,就让前面这个字母出栈。

例如,现在遍历到了字符串中的字符’a’,如果栈中上一个元素是’b’(’b’ > ‘a’),并且’b’以后还会再次出现,那么就让’b’出栈,等再次遇到’b’时,再让’b’重新入栈。

这就需要我们再开辟一个整数型的数组,来记录遍历到当前元素时,某个元素还会再出现多少次。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
string removeDuplicateLetters(string& s) {
// 预处理,统计每个字符出现了多少次
int remain[26] = {0};
for (char& c : s) {
remain[c - 'a']++;
}
// 单调栈开始
stack<char> st;
bool isInStack[26] = {false}; // 某个元素是否在栈中
for (char& c : s) { // 遍历字符串
remain[c - 'a']--; // 这个字符出现了一次,后面还会再出现的次数--
if (isInStack[c - 'a']) // 这个字符已经在栈中了
continue;
while (st.size() && st.top() > c && remain[st.top() - 'a']) { // 如果栈顶字符大于这个字符,并且栈顶字符还会再次出现,就让栈顶字符先出栈,后面再次遇到的时候再入栈
isInStack[st.top() - 'a'] = false;
st.pop();
}
// 这个字符入栈
st.push(c);
isInStack[c - 'a'] = true;
}
// 同理,弹出栈中的每一个字符,之后再反转一下字符串
string ans;
while (st.size()) {
ans += st.top();
st.pop();
}
reverse(ans.begin(), ans.end());
return ans;
}
  • 时间复杂度$O(n)$,其中$n$是字符串长度
  • 空间复杂度$O(C)$,其中$C$是字符集大小(26个小写英文字母,C=26)

推荐一篇描述更为详细的博客:由浅入深,单调栈思路去除重复字符

AC代码

C++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Solution {
public:
string removeDuplicateLetters(string& s) {
// 预处理,统计每个字符出现了多少次
int remain[26] = {0};
for (char& c : s) {
remain[c - 'a']++;
}
// 单调栈开始
stack<char> st;
bool isInStack[26] = {false};
for (char& c : s) {
remain[c - 'a']--;
if (isInStack[c - 'a'])
continue;
while (st.size() && st.top() > c && remain[st.top() - 'a']) {
isInStack[st.top() - 'a'] = false;
st.pop();
}
st.push(c);
isInStack[c - 'a'] = true;
}
string ans;
while (st.size()) {
ans += st.top();
st.pop();
}
reverse(ans.begin(), ans.end());
return ans;
}
};

同步发文于CSDN,原创不易,转载请附上原文链接哦~
Tisfy:https://letmefly.blog.csdn.net/article/details/127011739


316.去除重复字母
https://blog.letmefly.xyz/2022/09/23/LeetCode 0316.去除重复字母/
作者
Tisfy
发布于
2022年9月23日
许可协议