去除重复字母

题目

给你一个字符串 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;
    }
};