问题描述:
一个长度为 n 的字符串 s,其中仅包含 ‘Q’, ‘W’, ‘E’, ‘R’ 四种字符。如果四种字符在字符串中出现次数均为 n/4,则其为一个平衡字符串。现可以将 s 中连续的一段子串替换成相同长度的只包含那四个字符的任意字符串,使其变为一个平衡字符串,问替换子串的最小长度?如果 s 已经平衡则输出0。
input:
一行字符表示给定的字符串s
output:
一个整数表示答案
样例输入:
1:QWER
2:QQWE
3:QQQW
4:QQQQ
样例输出:
1: 0
2: 1
3: 2
4: 3
Note:
,
是4的倍数,字符串中仅包含字符 ‘Q’, ‘W’, ‘E’ 和 ‘R’.
解题思路:
该题目需要用到尺取法。它通常是维护一对下标,即所选取的区间的左右端点,然后根据实际情况不断的推进区间左右端店以得出答案。它是一种高效的枚举区间的方法,一般用于求取最短的区间等。
对于该题目,则是让我们求需要替换的最小子串的长度,也就是让我们求一个最小替换区间,则我们可以通过尺取法,枚举符合条件的每一个区间,其中找到最小的区间。
那怎么才算符合条件的区间呢?由于平衡字符串是每一个字符的个数都是字符串长度的四分之一,所以我们可以计算出除了已经走到的区间之外的各个字符的个数,然后用这个区间将四个字符数量补到相同(是向数量最多的字符看齐),再看剩余的空闲位置是否为4的倍数。若剩余的空闲个数小于0,那么就说明不能够补齐,不满足要求,右指针向右移,如果剩余位置不为4的倍数同理。如果两个条件都满足,那这个区间符合条件,则我们就尝试寻找一个更小的区间来判断是否符合条件,则我们将左指针右移,重复进行该过程,找到一个最小区间。
解题总结:
如果所求解答案为一个连续区间并且区间左右端点移动有明确方向,那么我们可以使用尺取法,可以高效的得到我们想要的区间。
代码:
#include<iostream>
#include<cstdio>
#include<map>
#include<string>
using namespace std;
int main()
{
map<char,int> mm;
mm['Q']=0;
mm['W']=1;
mm['E']=2;
mm['R']=3;
string ss;
int num[4]={0,0,0,0};
cin>>ss;
for(int i=0;i<ss.size();i++)
num[mm[ss[i]]]++;//计算每个字符的次数
int banum=ss.size()/4;
if(num[0]==banum&&num[1]==banum&&num[2]==banum&&num[3]==banum)
cout<<"0"<<endl;//若四个字符的数量相同,输出0
else
{
int n=ss.size();
int l=0,r=-1,ans=n;
bool flag=false;//判断一个区间是否满足
while(r<n){
while(l<=r&&flag)//如果该区间满足条件,缩小区间,l右移
{
num[mm[ss[l]]]++;//右移前将该字符算到区间外的字符数中
l++;
int maxn1=max(max(num[0],num[1]),max(num[2],num[3]));
//找到字符个数的最大值
int total1=r-l+1; //总的位置
int free1=total1-(maxn1-num[0])-(maxn1-num[1])-(maxn1-num[2])-(maxn1-num[3]);//剩余位置
if(free1>=0&&free1%4==0)//满足条件
{
flag=true;
if(total1<ans)
ans=total1;
}
else
flag=false;
}
r++;//右指针右移
num[mm[ss[r]]]--;//将字符个数减1
int maxn=max(max(num[0],num[1]),max(num[2],num[3]));
int total=r-l+1;
int free=total-(maxn-num[0])-(maxn-num[1])-(maxn-num[2])-(maxn-num[3]);
if(free>=0&&free%4==0)
{
flag=true;
if(total<ans)
ans=total;
}
else
flag=false;
}
cout<<ans<<endl;
}
}