题目
给你一个字符串 s ,请你去除字符串中重复的字母,使得每个字母只出现一次。需保证 返回结果的字典序最小(要求不能打乱其他字符的相对位置)。
示例 1:
输入:s = "bcabc"
输出:"abc"
示例 2:
输入:s = "cbacdcbc"
输出:"acdb"
提示:
1 <= s.length <= 104
s 由小写英文字母组成
思路
首先要搞清楚什么是字典序?顾名思义,是按照字典顺序排序。abcd...就是字典序。
在这个顺序下,a<b<c<d。如果是多个字符,比如abcd和acbd这两个字符串,
其字典序是abcd<acbd。从第一位开始比,如果相等则比较第二位,直到字符串的末尾。
如果有重复字符,删除不同位置的字符,会得到不同的结果。
答案就是所有结果中字典序最小的。那怎么删呢?重复的字符所在位置有两种:一是
相邻,二是不相邻,中间有其他的字符。相邻的重复字符,保留第一个,后面的全删掉就好。
不相邻的字符,有两种选择,删前和删后。如果中间的字符比重复字符大,此时前面的重复字符和
中间字符的排列位置是符合字典序的,所以要删后留前。如果中间的字符比重复字符小,此时后面的字符和
中间字符是符合字典序的,所以要删前留后。就是说尽量把重复的字符放在其字典序的位置。
比如bcabc,b和c是重复字符,要删b和c。根据要求,要删前面的b和c。
因为a是比c小的,所以要删前留后。删b也是同样的道理。基于以上考虑,首先要遍历字符串,记录重复情况。
然后再遍历字符串,如果不是重复字符,则保留,如果是重复字符,则看中间字符和重复字符的大小情况。
决定是删前留后还是删后留前。
代码
class Solution {
public:
string removeDuplicateLetters(string s) {
vector<int>book(26),num(26);
string ans;
//首先遍历数组,记录重复情况,显然大于1是重复的
for(char c: s) {
num[c-'a']++;
}
//再次遍历数组
for(char c: s) {
//当前字符未选中
if(book[c-'a']==0) {
//如果上次选中的字符大于当前字符
while(!ans.empty()&&ans.back()>c) {
//上次选中的字符是重复字符,此时要删前留后
if(num[ans.back()-'a'] > 0) {
book[ans.back()-'a'] = 0;
ans.pop_back();
}
else {
break;
}
}
//选中当前字符
book[c-'a'] = 1;
ans.push_back(c);
}
//选中之后,字符的数量减1
num[c-'a'] -= 1;
}
return ans;
}
};