VIRTUALS

the virtual labs for the virtuals

0%

LeetCode 316. 去除重复字母

摘要:
一道贪心题。

题目

给你一个字符串 $s$ ,请你去除字符串中重复的字母,使得每个字母只出现一次。需保证 返回结果的字典序最小(要求不能打乱其他字符的相对位置)。
注意:该题与 LeetCode 1081. 不同字符的最小子序列 相同

示例 1:

输入:s = “bcabc”
输出:”abc”

示例 2:

输入:s = “cbacdcbc”
输出:”acdb”

提示:

  • $1 <= s.length <= 10^4$
  • $s$ 由小写英文字母组成

贪心

首先将每个字符最后出现的位置记录下来,维护一个答案字符串。对于原字符串的每一个字符,判断答案字符串的最后一个字符是否大于它。如果大于它,而且这个字符在后面出现过,那么这个字符就不是最优的,要剔除。如果小于,则直接放入答案字符串。

如何证明方法的正确性?
现设想全部字符不重复,那么因为答案需要维护原字符之间的相对位置,那么答案就是原字符串。
如果其中的某些字符出现重复,那么当一个字符在之后的地方出现过,那么该字符在前面就是可被替换的。
现在考虑如果一个字符在之后也出现了,那么什么时候它可以被替换?
那就是它后方出现了一个比它小的新字符,那么现在需要作出决策,要么替换前方的在后面重复出现的字符,要么直接加在后面。
显然,替换掉属于最优解,因为只有这样整体字典序最小,且后面可以使用重复字符补上,不会改变字符之间的相对位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*
* author: yxc
*/
class Solution {
public:
string removeDuplicateLetters(string s) {
string stk;
unordered_map<char, bool> ins;
unordered_map<char, int> last;
int n = s.size();
for (int i = 0; i < n; i ++) last[s[i]] = i;

for (int i = 0; i < n; i ++) {
if (ins[s[i]]) continue;
for (; stk.size() && stk.back() > s[i] && last[stk.back()] > i;) {
ins[stk.back()] = false;
stk.pop_back();
}
stk += s[i];
ins[s[i]] = true;
}
return stk;
}
};
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
/*
* 2021-6-27 23:35:39
* author: etoa
*/
class Solution {
public:
static constexpr int N = 123;
int last[N], st[N];
string stk;
string removeDuplicateLetters(string s) {
int len = s.size();
for (int i = 0; i < len; i++) {
last[s[i]] = i;
}

for (int i = 0; i < len; i++) {
if (st[s[i]]) continue;
for (; stk.size() && s[i] < stk.back() && last[stk.back()] > i;) { // 如果栈非空,且当前字符小于栈顶字符,且栈顶字符在当前字符之后出现过,那么用当前字符替换掉栈顶字符将必然更优
st[stk.back()] = 0;
stk.pop_back();
}
stk += s[i]; // 此时栈要么被弹出一些元素,要么是不满足上面的条件,但是两种都需要将当前元素入栈。
st[s[i]] = 1;
}
return stk;
}
};