Leet code 1653.使字符串平衡的最少删除次数

题目重现:

思路介绍:

        写在前面:

                对于本题,我记录了我自己的做法(法一),官方做法(法二),大佬做法(法三),我自己的做法有点新意(qi guai),所以其实可以直接去看后两种...我在这里给出我的做法的正确性说明

法一:

        对于本题,我们很容易抽象出来一个简单的模型,s是由A0个a,B0个b,A1个a,B1个b...一直到An个a,Bn个b."aababbab"就是2个a,1个b,1个a,2个b,1个a,1个b,这里我们可以很容易使得a序列的n和b序列的n相等,因为如果是b开始,如示例2,就是0个a,2个b,5个a,2个b;同时我们把这些个Ai,Bi放入vector<int> a_count,b_count中.

        刚看见这个题,可能觉得无从下手,但是如果我们抽离出来一个简单的模型之和,反而便于观察,现在再去看这个题,会发现我们需要在A0B0...AnBn里面包含两边界的2n+1个空隙中找一个位置放上分割线 || 使得线前的Bi全部删去,线后的Ai全部删去.

        简单思考一下,其实我们并不需要2*n+1个选择,我们其实只有n个选择这n个选择是插在每一组Ai || Bi之中(也就等价与找到一组Ai Bi使得删去的个数最少),我们实际上是要找到一组相邻的aa...abb...b(即AiBi),然后把i前的所有Bi-1,Bi-2...B0删去,i后面的Ai+1,Ai+2...An删去,然后使得删去的总个数最少就可以了,同样的如果我们删去除了A0以外的所有的a,那也就是选择了第一组,而同样的如果我们选择最后一组,就意味着我们要删去除了Bn以外的所有b.(注意:其实A0,Bn不会影响最后的结果)

        因为如果我们插在Ai Bi || Ai+1 Bi+1之间,比起插在Ai || Bi 之间其实是多删除了一个Bi,比起插在Ai+1 Bi+1之间是多删除了一个Ai+1,完全没有必要这么做.

        代码如下:

    int minimumDeletions(string s) {
        vector<int> a_count;
        vector<int> b_count;
        bool flag_a=true;
        for(int i=0;i<s.size();){
            int temp=0;
            if(flag_a){
                while(s[i]=='a') {i++; temp++;}
                a_count.push_back(temp);
            }else{
                while(s[i]=='b') {i++; temp++;}
                b_count.push_back(temp);
            }
            flag_a=!flag_a;
        }
        if(flag_a==false) b_count.push_back(0);//末尾没有b是0个,这个操作保证a_count,b_count个数相等
        
        for(int i=a_count.size()-2;i>=0;i--){ 
            a_count[i] += a_count[i+1];
        }//把a_count里面的数换成从末尾走到该分组(包括该分组)中所有的a 可简化运算


        for(int i=1;i<b_count.size();i++){
            b_count[i] += b_count[i-1];
        }//把b_count里面的数换成从开始走到该分组(包括该分组)中所有的b 可简化运算


        if(a_count.size()==1) return 0;//防止越界访问

        int min=INT_MAX; 
        for(int i=0;i<a_count.size();i++){
            int sum=0;
            if(i==0){ sum=a_count[1]; }
            else if(i==a_count.size()-1){
                sum=b_count[a_count.size()-2];
            }else{
                sum=b_count[i-1]+a_count[i+1];//其实总的只有这个式子,另外两个if是为了防止越界访问
            }
            if(sum<min) min=sum;
        }
        return min;
    }

 法二:

        如果我们抓住每一个单词字母来看,就会发现这个题是对于s字符串的n+1个空隙(n==s.size()),找到一个插入||分隔符,使得要删去的左边的b和右边的a的个数的总和最小,min(leftb+righta)

        我们只需要两遍对s的遍历即可,第一遍求出a的总数sum_a作为初始化的值,第二遍遍历s[i],如果s[i]=='a' righta--,如果s[i]=='b' leftb++

        代码如下:

    int minimumDeletions(string s) {
        int leftb = 0, righta = 0;
        for (int i = 0; i < s.size(); i++) {
            if (s[i] == 'a') {
                righta++;
            }
        }
        int res = righta;
        for (int i = 0; i < s.size(); i++) { 
            if (s[i] == 'a') {
                righta--;
            } else {
                leftb++;
            }
            res = min(res, leftb + righta);
        }
        return res;
    }

法三:动态规划

        对于这个题,我自己是有想过使用动态规划的,但是不知道从哪里下手这里提供评论区的@Tammon的两种思路:

        1.计算可以达到最大的长度len.返回s.size()-len.

        最终是所有a在所有b后面,我们使用len_a记录最终以a结尾的最大长度,len_b记录最终以b结尾的最大长度,如果当前的s[i]=='a',len_a++,而如果当前的s[i]=='b',len_b=max(len_a,len_b)+1,加的这个1就是最后的这个('b')

    int minimumDeletions(string s) {
        int len_a = 0, len_b = 0, n = s.size();
        for (char x : s) {
            if (x == 'a') len_a ++;
            else b = max(len_a, len_b) + 1;
        }
        return n - max(len_a, len_b);
    }

        2.计算要删去的最小长度 同@落霞与孤鹜亓飞

        我们使用dp[i]记录当前以下标i元素结尾所要删去的最少元素,当我们的s[i]=='a'的时候,我们有两种做法,一种是与dp[i-1]联系起来,直接删除s[i]处的a,第二种是删除之前出现的所有的b--count_b,而当s[i]=='b'时,count_b++即可;

    int minimumDeletions(string s) {
        int ans= 0, count_b= 0;
        for (int i = 0; i < s.size(); ++i) {
            if (s[i] =='b') ++count_b;
            else
            ret = min(count_b, 1 + ans);
        }
        return ans;
    }

        动态规划是一种简化了的思想,它不要求我们直接从条件看到结论的得来,而是可以建立在假设的基础上,进一步简化了思维深度,而一个好的假设就是解题的关键,这往往和题目相关但是也会有一些通用的做法.当我们思考不出来dp做法时,可以用多用数组建立dp[i]与dp[i-1]的关系而不是直接ans=ans... 会更有利于理解.

        这是自己的学习笔记,侵删.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值