[week5]平衡字符串——尺取法

题意

一个长度为 n 的字符串 s,其中仅包含 ‘Q’, ‘W’, ‘E’, ‘R’ 四种字符。

如果四种字符在字符串中出现次数均为 n/4,则其为一个平衡字符串。

现可以将 s 中连续的一段子串替换成相同长度的只包含那四个字符的任意字符串,使其变为一个平衡字符串,问替换子串的最小长度?

如果 s 已经平衡则输出0。

Input

一行字符表示给定的字符串s

Output

一个整数表示答案

输入样例

QWER


QQWE


QQQW


QQQQ

输出样例

0


1


2


3

提示


分析

解决这道题的关键在于理解题目和尺取法的应用。


  • 尺取法

尺取法,又称作双指针法,是一种用在数组上往相同方向扫描的方法。

该方法一般用于寻找序列中一段满足要求的连续区间,且移动方向较明确。

  • 具体操作

设置两个伪指针,分别代表当前囊括区间的左右边界,通过移动这两个指针实现扫描区间的移动。当区间符合和不符合要求的时候分别进行不同的移动,并在过程中不断记录和更新答案,直到扫描完所有子区间。

🌰——寻找符合要求的最小区间

若当前包括区间不符合要求,则将右指针前移,增大区间,直到满足要求;若当前区间符合要求,则将左指针前移,缩小区间,直到不满足要求;若左指针前移后左右指针重叠,则将右指针也前移一位。

当左右指针都移动到了序列尽头后,扫描结束。在整个扫描过程中,每次满足要求时都需要更新答案。在扫描结束后留下的答案即为最佳答案。


  • 题目分析

  • 可以使用尺取法吗?

题目要求要在字符串中选出连续的一段字符进行替换。这说明该扫描目标是寻找一段连续区间,且显然移动方向是明确的,即左右指针都从左向右扫描。

因此,我们可以使用尺取法。

  • 如何确定当前扫描的区间是否符合要求?

题目要求四个字符在字符串中出现次数均为n/4。

这代表:

  1. 每个字符个数相同
  2. 每个字符个数为4的倍数

若当前扫描的区间符合要求,则替换后整个字符就能变为平衡字符串。

这意味着:

  1. 区间内的字符个数至少能让区间外的所有字符的个数暂时相同
  2. 若当前区间外字符的个数暂且相同,区间内的字符,其个数一定为4的倍数

因此我们可以假设,若先利用区间内的部分字符进行替换,使区间外所有字符的个数都达到当前区间外最多字符的个数,即让所有字符个数都暂且达到相等。那么,如果区间内剩下的字符个数是4的倍数,则该区间一定可以满足要求。

由于题目并不要求具体的替换次数和操作,因此解决该问题的设计都可以建立在字符数的基础上。


  • 代码实现

左右指针从起点开始扫描。若左指针大于右指针时,右指针前移。

遍历每个区间时,计算区间中字符的总个数、当前区间外每个字符的个数,以及区间内字符总数减去每个区间外字符与区间外最大字符数差得到的差。

区间内剩余字符数 = 区间内总数 - ( 区间外单个字符数max - 区间外字符1个数 ) - ( 区间外单个字符数max - 区间外字符2个数 ) - ( 区间外单个字符数max - 区间外字符3个数 ) -( 区间外单个字符数max - 区间外字符4个数 )

对区间内剩余字符数进行判定,若为4的倍数,则该区间符合要求,左指针前移,缩小区间;否则,右指针前移,扩大区间。


  • 代码优化和遇到的问题
  1. 遇到的问题

在左右指针的移动过程中,很容易判断什么情况下移动哪一个指针。但是整个扫描过程的结束是当左指针也遍历完所有元素时。

因此在遍历的最后阶段一定会出现这样的情况:当前区间不符合要求,但是右指针已到达最右。

由于我们需要遍历所有子区间,以保证考察所有情况。因此在这种情况下,应该将左指针继续前移,依次遍历完剩下的区间。即,当右指针到达最右后,不论当前区间是否符合要求,左指针都将前移。区别在于,仅当区间符合要求时,才会考虑更新答案。

  1. 若初始字符串已经为平衡字符串

在这种情况下,我们已经不需要再进行任何遍历。因为不用改变任何字符,也就是区间为0时,即为最佳答案。

因此我们可以直接在第一次统计初始字符串中的每个字符数之后,就直接进行验证,若已经为平衡字符串,则直接输出0,并结束程序。

  1. 性能优化

在这个代码实现中,显然最耗时的是统计字符个数。

虽然通过count可以快速统计任意区间内的字符个数,但是若在整个遍历过程中,每一次的遍历都要重新在新区间内统计字符个数,这显然会超时。

仔细想一下就会发现,其实每一次的遍历,都是左右指针的移动。而左右指针实际上每一次都只会移动一格,也就意味着每次变化的字符个数,不论区间内外,实际上最多都只为一个字符,且这个字符可以被唯一确定。而我们遍历开始的区间原本是由整个区间的起始位置从长度1开始的,即第一个字符在区间外的个数-1。

因此,只需要在最开始统计输入字符串的初始情况,再在每一次遍历时根据左指针还是右指针的移动,来确定当下的各标量。

⚠️左指针前移,则代表区间外的某字符个数+1,区间内总个数-1;右指针前移,则代表区间外某字符个数-1,区间内总个数+1。


总结

  1. 脑瓜较蠢,超时优化还要想半天😐
  2. 我为自己一下子想到求四个数最大值的小式子开心了半天【hhh】

代码

//
//  main.cpp
//  lab-c
//
//

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;

string s;

int main()
{
    ios::sync_with_stdio(false);
    
//    s.resize(1e5);
    
//    scanf("%s",&s[0]);
    cin>>s;
     
     
    long long n = s.size(),c1 = 0,c2 = 0,c3 = 0,c4 = 0,l = 0,r = 0,m = 0,free = 0,sum = 0,ans = 1e5;//a1 = 0,a2 = 0,a3 = 0,a4 = 0;
//    bool judge = false;
    
    c1 = count(s.begin(), s.end(), 'Q');            //统计字符串中所有字符个数
    c2 = count(s.begin(), s.end(), 'W');
    c3 = count(s.begin(), s.end(), 'E');
    c4 = count(s.begin(), s.end(), 'R');
    
    if( c1 == c2 && c3 == c4 && c2 == c3 )          //若已符合要求则结束
    {
       cout<<0<<endl;
        return 0;
    }
    else                    //否则,第一个字符在在区间外的字符个数-1
    {
        switch (s[0])
        {
            case 'Q':
                c1--;
                break;
            case 'W':
                c2--;
                break;
            case 'E':
                c3--;
                break;
            case 'R':
                c4--;
                break;
            default:
                break;
        }
    }
    
    while ( l < n )
    {
        if( l > r )
            r++;
        
//        cout<<l<<" / "<<r<<endl;
        
        
        //求出除当前区间外的四个字符的数量(这样写会超时)
        
/*        c1 = a1 - count(s.begin()+l,s.begin()+r+1,'Q');
        c2 = a2 - count(s.begin()+l,s.begin()+r+1,'W');
        c3 = a3 - count(s.begin()+l,s.begin()+r+1,'E');
        c4 = a4 - count(s.begin()+l,s.begin()+r+1,'R');*/
        
        
//        cout<<c1<<" "<<c2<<" "<<c3<<" "<<c4<<" ** "<<endl;
        
        m = max(max(c1, c2),max(c3,c4));            //得到其中数量最多的字符个数
        
        sum = r - l + 1;                    //区间包含字符总数
           
        //将区间内的部分字符替换,使得当前所有字符数量相等,每个字符需要替换的数量即为当前与最大值的差
        free = sum - ( (m - c1) + (m - c2) + (m - c3) + (m - c4) ); //得到此时区间中剩下还未替换的字符数
        
//        cout<<m<<"-----"<<sum<<"-----"<<free<<endl;
        
        if( free >= 0 && free % 4 == 0 )        //若剩余字符数为4的倍数,则一定满足要求
        {
            l++;                    //此时左指针缩小范围
            
            switch (s[l-1])         //则上一个字符在区间外的字符数+1
            {
                case 'Q':
                    c1++;
                    break;
                case 'W':
                    c2++;
                    break;
                case 'E':
                    c3++;
                    break;
                case 'R':
                    c4++;
                    break;
                default:
                    break;
            }
            
            if( sum < ans )         //记录最小区间长度
                ans = sum;
        }
        else
        {
            if( r < n - 1 )
            {
                r++;
                switch (s[r])       //则当前新增字符在区间外的字符数-1
                {
                    case 'Q':
                        c1--;
                        break;
                    case 'W':
                        c2--;
                        break;
                    case 'E':
                        c3--;
                        break;
                    case 'R':
                        c4--;
                        break;
                    default:
                        break;
                }
            }
            else
            {
                l++;
                switch (s[l-1])         //则上一个字符在区间外的字符数+1
                {
                    case 'Q':
                        c1--;
                        break;
                    case 'W':
                        c2--;
                        break;
                    case 'E':
                        c3--;
                        break;
                    case 'R':
                        c4--;
                        break;
                    default:
                        break;
                }
            }
        }
    }
    
        cout<<ans<<endl;

    
    return 0;
}



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天翊藉君

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值