题目描述
给你一个字符串 s ,请你去除字符串中重复的字母,使得每个字母只出现一次。需保证 返回结果的
字典序
最小(要求不能打乱其他字符的相对位置)。
示例 1:
输入:s = “bcabc”
输出:“abc”
示例 2:
输入:s = “cbacdcbc”
输出:“acdb”
提示:
1 <= s.length <= 104
s 由小写英文字母组成
解题思路
class Solution {
public:
string removeDuplicateLetters(string s) {
unordered_map<char, int> count;
unordered_set<char> inStack;
stack<char> stk;
for (auto c: s) {
count[c]++;
}
for (auto c: s) {
count[c]--;
if (inStack.count(c)) {
continue;
}
while (!stk.empty() && c < stk.top() && count[stk.top()]) {
inStack.erase(stk.top());
stk.pop();
}
stk.push(c);
inStack.insert(c);
}
string ans;
while (!stk.empty()) {
ans += stk.top();
stk.pop();
}
reverse(ans.begin(), ans.end());
return ans;
}
};
代码分析
class Solution {
public:
string removeDuplicateLetters(string s) {
unordered_map<char, int> count; // 存储每个字符的出现次数
unordered_set<char> inStack; // 记录栈中已有的字符,快速检查一个字符是否在栈中
stack<char> stk; // 用来存储最终结果的字符,确保字符的顺序和唯一性
- 计数字符频率:
- 使用
unordered_map
来记录每个字符在字符串s
中出现的次数。 - 遍历字符串
s
,为每个字符的出现次数计数。
- 使用
for (auto c: s) {
count[c]++;
}
- 遍历字符串进行处理:
- 再次遍历字符串
s
,对于每个字符c
,首先减少其在count
中的计数(表示这个字符被处理过一次)。 - 如果字符已在栈中(通过
unordered_set
检查),则跳过当前字符,因为我们要保证每个字符在结果中只出现一次。
- 再次遍历字符串
for (auto c: s) {
count[c]--;
if (inStack.count(c)) {
continue;
}
- 栈操作保证字典序:
- 检查当前栈是否为空,栈顶字符是否大于当前字符
c
,并且栈顶字符在后面还会出现(通过count[stk.top()] > 0
检查)。 - 如果以上条件都满足,弹出栈顶字符,并在
inStack
中移除该字符。 - 将当前字符
c
压入栈,并在inStack
中记录。
- 检查当前栈是否为空,栈顶字符是否大于当前字符
while (!stk.empty() && c < stk.top() && count[stk.top()]) {
inStack.erase(stk.top());
stk.pop();
}
stk.push(c);
inStack.insert(c);
}
- 构建最终结果字符串:
- 从栈中弹出所有字符并添加到结果字符串
ans
。 - 由于栈是后进先出的,我们需要反转字符串
ans
以获得正确的顺序。
- 从栈中弹出所有字符并添加到结果字符串
string ans;
while (!stk.empty()) {
ans += stk.top();
stk.pop();
}
reverse(ans.begin(), ans.end());
return ans;
}
};
复杂度分析
- 时间复杂度:O(N),其中 N 是字符串
s
的长度。每个字符最多被推入和弹出栈一次。 - 空间复杂度:O(1),虽然使用了额外的数据结构,但由于输入限制为小写字母,因此空间使用量被限制在常数级别。
结论
这个算法利用了栈的特性和贪心的思想,高效地解决了移除重复字符并保证字典序最小的问题。使用栈来维护字符顺序和 unordered_set
快速查找已处理的字符是此解法的关键。