316. 去除重复字母
题目描述
给你一个字符串 s ,请你去除字符串中重复的字母,使得每个字母只出现一次。需保证 返回结果的字典序最小(要求不能打乱其他字符的相对位置)。
示例1
输入:s = “bcabc”
输出:“abc”
示例2
输入:s = “cbacdcbc”
输出:“acdb”
解题思路
思路一: 栈
题目要求有三点:
- 去重;
- 去重字符串中的字符顺序不能打乱s中字符出现的相对顺序;
- 结果为字典序最小的字符串。
先来实现要求1、2
, 这里我们使用「栈」这种数据结构来实现:
实现代码如下:
/**
* @param {string} s
* @return {string}
*/
var removeDuplicateLetters = function(s) {
// 存放去重的结果
let stack = [],
// 记录栈中是否存在某个字符
vis = {};
for (let c of s) {
// 如果字符 c 存在栈中,直接跳过
if (vis[c]) continue;
// 若不存在,则插入栈顶并标记为存在
stack.push(c);
vis[c] = true;
}
return stack.join('');
};
这段代码基本上满足需求1、2
, 但怎么要保证字典序最小呢?
我们在向栈stack中插入字符时, 我们插入一个while循环,pop栈顶字符与当前字符进行比较, 直到栈顶元素比当前元素的字典序还小为止。
此时,还有个问题需要解决: 如果pop出的栈顶元素,且后续字符没有该字符如何处理?
例: s=‘bcabc’, 在插入’a’时,发现前面的字符’c’字典序要大,且在 ‘a’ 之后还存在字符 ‘c’,那么栈顶的这个 ‘c’ 就会被 pop 掉。
while 循环继续判断,发现前面的字符 ‘b’ 的字典序还是比 ‘a’ 大,但是在 ‘a’ 之后再没有字符 ‘b’ 了,所以不应该把 ‘b’ pop 出去。
此时我们可以维护一个计算器记录字符串中每个字符的数量,如果次数大于1, 则可以pop。 这样就可以解决这个问题。
实现代码如下:
/**
* @param {string} s
* @return {string}
*/
var removeDuplicateLetters = function(s) {
let stack = [],
vis = {},
count = {};
for (let c of s) {
if (count[c]) {
count[c]++;
} else {
count[c] = 1;
}
}
for (let c of s) {
// 每遍历过一个字符,都将对应的计数减一
count[c]--;
if (vis[c]) continue;
while (stack.length && stack[stack.length - 1] > c) {
// 若之后不存在栈顶元素了,则停止 pop
if (count[stack[stack.length - 1]] == 0) {
break;
}
// 若之后还有,则可以 pop
vis[stack.pop()] = false;
}
stack.push(c);
vis[c] = true;
}
return stack.join('');
};
- 时间复杂度: O(n); 其中 n 为字符串长度, 代码中虽然有双重循环,但是每个字符至多只会入栈、出栈各一次。
- 空间复杂度: O(n);