Remove Duplicate Letters 删除重复元素

本文探讨了一种算法,该算法可以输入一个字符串并删除其中的重复字符,同时确保剩余字符的排列是最小的字典序。通过两次遍历的方法,首先统计每个字符的出现次数,然后决定是否保留当前字符,以达到最终的目标。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、概述

输入一个字符串,删除其中重复的字符,并保证剩余的元素排列是所有可能性中最小的。
这什么意思?
以cbacdcbc为例,删完重复的,剩下abcd四个字符,那么我可以这样删:
c b a c d c b c
c b a x d x x x
结果就是cbad
也可以这样删
x x a c d x b x
结果就是acdb,这个字典序比cbad小,要这个。

二、分析

麻烦。很复杂。我自己分析的贪心算法都只能找到局部最优,找不到全局最优。
先想一个问题,我们只遍历一遍的话,如何判断当前的字符放不放到结果中?
以字符串cbacdcbc为例
首先是c,放入结果,结果为c
然后是b,放入结果,结果为cb
然后是a,放入结果,结果为cba
然后是c,结果中已经有c了,那这两个c要哪个呢?要前面那个是cba,后边这个是bac,那肯定要后边这个不要前边这个
那我这么看太麻烦了不说,还有这种情况:
对于字符串bcabc
首先是bca,结果是bca,
然后是b,结果中已经有b了,那要之前的b还是这个b?要之前的b,结果是bca,这个b,结果是cab,要之前的。
然后是c,结果中已经有c了,那要之前的c还是这个c?要之前的c,结果是bca,这个c,结果是bac,要这个。
所以结果是bac。结果错了。
应该是abc。为什么?因为我们应该要第二个b而不是第一个。
也就是说,不能只遍历一次,只遍历一次只能得到局部最优,无法得到全局最优。

正确算法是遍历两次。第一次知道各个字符的储量。仍然以cbacdcbc为例。
先遍历一次探明储量。a有一个,b有2,c有4,d有1。
然后遍历第二次:
首先是c,放入结果,结果为c
然后是b,先看结果,结果中有c,现在有b,是委屈一下b,把b放在c后面,结果是bc,还是不要这个c了,结果仍然是b?
也就是说,这个c到底要不要?那得看后面有没有c,后面还有c,那么必定最后有结果…b…c;后面没有c,那么这个独苗就得保住。之前已经探明了储量,有c,那么这个c就滚蛋,目前结果是b。
总结起来就是:保这个c,结果是cb…,不保,结果是b…c…。我们要字典序最小,那么肯定要后者。
之后是a。同理,由于b比a大,是委屈这个a,结果是ba,还是不要这个b,结果是a。看b的储量。b有俩,那不客气了,b滚蛋
结果为a
之后是c,尾巴是a,比c小,放入结果,结果是ac。
之后是d,尾巴是c,放入结果,结果是acd。
之后是c,结果中已经有c了,那么这个c就不用看了。因为结果中一定是当前最优的。
之后是b,尾巴是d,比b大,想删,只有一个,删不了,那b就只能放在最后,结果是acdb。
以此类推。
总结一下:先探明各个字符的储量。然后遍历一次想得到结果。就得拿捏好到底要哪个字符。我们要求字符序越小越好,也就是说小的字符越在前越好。
那么,每当遇到一个结果中没有的字符,就从最后一个当前的结果开始比,最后一个字符比当前字符大,储量还有,那么就把它删了,否则就把当前字符放在最后。
核心就是b…c…一定比cb好。那么我就把cb这个c撇了,反正之后还有。优化结果更重要。
先保证有无,再保证字典序最小。

三、总结

有点晕,这个算法不太好想,得明确贪心的规则:
当前字符在结果中已存在?
是,跳过;否,结尾字符储量还有?
否,当前字符放在最后;是,当前字符比最后一个字符小?
否,当前字符放到最后;是,删除最后一个,继续和最后一个比较。
PS:代码如下:

class Solution {
public:
    string removeDuplicateLetters(string s) {
        
        string res="";
        vector<int> HT(256,0);
        for(auto i:s)
            HT[i]++;
        for(auto i:s)
        {
            HT[i]--;
            if(res=="")
            {
                res+=i;
                continue;
            }
            else if(res.find(i)!=string::npos)
                continue;
            else
            {
                while(res!=""&&HT[res[res.size()-1]]>0&&res[res.size()-1]>i)
                    res.pop_back();
                if(res.find(i)==string::npos)
                    res+=i;
            }
        }
        return res;
    }
}; 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值