Medium
题目描述
给你一个字符串 s,请你去除字符串中重复的字母,使得每个字母只出现一次。需保证返回结果的字典序最小(要求不能打乱其他字符的相对位置)。
示例 1:
输入:s = "bcabc"
输出:"abc"
示例 2:
输入:s = "cbacdcbc"
输出:"acdb"
提示:
1 <= s.length <= 10^4s由小写英文字母组成
**注意:**本题与 1081 题相同。
解题思路
这是一道经典的单调栈 + 贪心算法题。
核心思路:我们需要构建一个字典序最小的去重字符串。关键在于决定何时保留当前字符,何时移除之前的字符。
算法步骤:
- 统计频次:首先统计每个字符在字符串中的出现次数
- 单调栈处理:遍历字符串,对每个字符:
- 如果该字符已在结果中,跳过并减少计数
- 否则,检查栈顶元素是否大于当前字符且后续还会出现,如果是则弹出栈顶
- 将当前字符入栈并标记为已使用
- 贪心策略:当遇到较小字符时,如果前面较大的字符在后续还会出现,就可以安全地移除它们
为什么这样做是正确的?
- 单调栈保证了字典序最小
- 频次统计确保我们不会错误地移除唯一出现的字符
- 贪心策略保证每次都做出局部最优选择
时间复杂度O(n),空间复杂度O(1)(因为最多26个字母)。
代码实现
class Solution {
public:
string removeDuplicateLetters(string s) {
vector<int> count(26, 0);
vector<bool> inStack(26, false);
// 统计每个字符的出现次数
for (char c : s) {
count[c - 'a']++;
}
string result = "";
for (char c : s) {
count[c - 'a']--;
// 如果字符已经在结果中,跳过
if (inStack[c - 'a']) {
continue;
}
// 当栈顶字符大于当前字符且后面还会出现时,弹出栈顶
while (!result.empty() && result.back() > c && count[result.back() - 'a'] > 0) {
inStack[result.back() - 'a'] = false;
result.pop_back();
}
result += c;
inStack[c - 'a'] = true;
}
return result;
}
};
class Solution:
def removeDuplicateLetters(self, s: str) -> str:
# 统计每个字符的出现次数
count = {}
for c in s:
count[c] = count.get(c, 0) + 1
stack = []
in_stack = set()
for c in s:
count[c] -= 1
# 如果字符已经在栈中,跳过
if c in in_stack:
continue
# 当栈顶字符大于当前字符且后面还会出现时,弹出栈顶
while stack and stack[-1] > c and count[stack[-1]] > 0:
removed = stack.pop()
in_stack.remove(removed)
stack.append(c)
in_stack.add(c)
return ''.join(stack)
public class Solution {
public string RemoveDuplicateLetters(string s) {
int[] count = new int[26];
bool[] inStack = new bool[26];
// 统计每个字符的出现次数
foreach (char c in s) {
count[c - 'a']++;
}
List<char> stack = new List<char>();
foreach (char c in s) {
count[c - 'a']--;
// 如果字符已经在栈中,跳过
if (inStack[c - 'a']) {
continue;
}
// 当栈顶字符大于当前字符且后面还会出现时,弹出栈顶
while (stack.Count > 0 && stack[stack.Count - 1] > c && count[stack[stack.Count - 1] - 'a'] > 0) {
char removed = stack[stack.Count - 1];
stack.RemoveAt(stack.Count - 1);
inStack[removed - 'a'] = false;
}
stack.Add(c);
inStack[c - 'a'] = true;
}
return new string(stack.ToArray());
}
}
var removeDuplicateLetters = function(s) {
const count = new Map();
const inStack = new Set();
// 统计每个字符的出现次数
for (const c of s) {
count.set(c, (count.get(c) || 0) + 1);
}
const stack = [];
for (const c of s) {
count.set(c, count.get(c) - 1);
// 如果字符已经在栈中,跳过
if (inStack.has(c)) {
continue;
}
// 当栈顶字符大于当前字符且后面还会出现时,弹出栈顶
while (stack.length > 0 && stack[stack.length - 1] > c && count.get(stack[stack.length - 1]) > 0) {
const removed = stack.pop();
inStack.delete(removed);
}
stack.push(c);
inStack.add(c);
}
return stack.join('');
};
复杂度分析
| 复杂度类型 | 复杂度 | 说明 |
|---|---|---|
| 时间复杂度 | O(n) | 每个字符最多入栈和出栈一次 |
| 空间复杂度 | O(1) | 使用固定大小的数组和集合(最多26个字母) |