题面
Given a string which contains only lowercase letters, remove duplicate letters so that every letter appear once and only once. You must make sure your result is the smallest in lexicographical order among all possible results.
Example:
Given "bcabc"
Return "abc"
Given "cbacdcbc"
Return "acdb"
题目的意思是删除给定字符串中重复的字符,是去重后的字符串拥有最小字典序。
分析
题目的描述很简单,但是实现起来却比较困难。
仔细分析一下约束条件:
- 首先是去重,在目标字符串中,所有的字符出现且仅出现一次。
- 目标字符串是所有去重字符串中拥有最小字典序的一个
首先对于第一个要求,实现起来比较简单,暴力的方法,可以通过将所有满足去重要求的字符串全部产生出来,然后找出拥有最小字典序的一项。
但是这样的效率肯定不是我们所期望的……
必须在慎重选择去重的策略,在完成去重后刚好生成最小字典序的目标字符串。
遍历输入字符串,对于每个遍历到的字符,是否去重的情况如下:
- 如果该字符在字符串中仅出现过一次,那么必然出现在目标字符串中,不能去重
- 如果该字符在字符串中出现多次:
- 如果该字符没有被添加到结果字符串,可以选择添加
- 如果该字符已经被添加到结果字符串,决定是否去除结果中的该字符,在结果字符串的末尾重新添加该字符。
下面说一说去重的策略。
既然要求结果字符串需要包含所有输入字符串中的所有字符一次,那么先尝试将还未在结果字符串中出现的字符添加到结果字符串中。
在添加之前,就可以进行去重的操作了。从当前已经生成的结果字符串的最后一个字符开始判断该字符能否被去掉。而判断的条件就是该字符是否大于将要加进结果字符串的字符以及该字符是否在输入字符串中不再出现。为了比较容易取得结果字符串的最后一个字符,以及 更好地实现去掉该字符的操作。可以选择栈或者双向链表来作为存储结果字符串的数据结构。
我们在每次添加字符到结果字符串时,假设结果字符串是在遍历到当前字符时获得的字典序是最小的,且保留了所有的字符都将出现且仅出现在结果字符串中的可能。是一个逐步择优的过程。
代码
string removeDuplicateLetters(string s) {
//represent the character has been added to the result string
vector<bool> added;
added.assign(26, false);
vector<int> count;
count.assign(26, 0);
//used to store the reuslt string
stack<char> result_stack;
//traverse and compute the times every character appear
for (int i = 0; i < s.size(); i++) {
count[s[i] - 'a']++;
}
for (int i = 0; i < s.size(); i++) {
count[s[i] - 'a']--;
if (added[s[i] - 'a']) continue;
//pop all removable character
while (!result_stack.empty() && result_stack.top() >= s[i] && count[result_stack.top() - 'a'] > 0) {
added[result_stack.top() - 'a'] = false;
result_stack.pop();
}
result_stack.push(s[i]);
added[s[i] - 'a'] = true;
}
string result_string = "";
while (!result_stack.empty()) {
result_string += result_stack.top();
result_stack.pop();
}
reverse(result_string.begin(), result_string.end());
return result_string;
}