字符去重并返回字典序最小的结果
字符依次入栈
如果栈顶元素大于当前字符 则出栈
保证当前字符入栈时 是栈中最大的元素
如此 从栈底到栈顶的元素依次排列得到的就是字典序较小的子序列
但是出栈还要加上其他限制条件 仅比较大小 最后保留的只会是串中最小的字符
去重要求相同的字符仅保留一个且必须保留一个
什么时候栈顶元素可以直接弹出呢
除了满足栈顶大于当前字符 还要保证被弹出的字符后续还能再遇到
这就需要维护一个字符出现次数统计数组 记录后续字串的某字符剩余数量 当剩余数量为0时 即便栈顶偏大也不能再丢弃
什么时候当前字符可以入栈呢
除了满足大于当前栈内所有元素以外(因为单调且有的字符必须保留 所以只要大于栈顶元素或者遇到不能弹出的栈顶元素即可)
还要保证栈内没有与它重复的字符(如果严格单调 那么只需大于栈顶元素 就可以保证不重 但是 存在不能弹出的元素 所以无法判定栈内有没有相同的元素) 因此需要维护一个布尔数组 记录当前栈内元素存在情况
因此入栈时要更新依次状态 出栈时也要再更新依次
注意到如果有一个字符 已经出现在了栈内 那么它必定不能被加入 直接continue
就行
class Solution {
public String removeDuplicateLetters(String s) {
int[] cnt = new int[30]; // 记录总的剩余次数
boolean[] vis = new boolean[30]; // 记录存在状态
StringBuilder sb = new StringBuilder(); // 方便转字符串 替代栈
for (int i = 0; i < s.length(); i++) cnt[s.charAt(i) - 'a']++; // 统计字符出现次数
for (int i = 0; i < s.length(); i++) {
char ch = s.charAt(i);
int idx = ch - 'a';
cnt[idx]--; // 更新剩余次数
if (vis[idx]) continue; // 如果已经出现 直接跳过
while (
sb.length() != 0 && // 栈不空
sb.charAt(sb.length() - 1) > ch && // 栈顶元素大于当前字符
cnt[sb.charAt(sb.length() - 1) - 'a'] > 0 // 且栈顶元素可丢弃
) {
vis[sb.charAt(sb.length() - 1) - 'a'] = false; // 更新存在状态
sb.deleteCharAt(sb.length() - 1); // 移除栈顶元素
}
vis[ch - 'a'] = true; // 更新存在状态
sb.append(ch); // 入栈
}
return sb.toString();
}
}