题目描述:
一个长度为 n 的字符串 s,其中仅包含 ‘Q’, ‘W’, ‘E’, ‘R’ 四种字符。
如果四种字符在字符串中出现次数均为 n/4,则其为一个平衡字符串。
现可以将 s 中连续的一段子串替换成相同长度的只包含那四个字符的任意字符串,使其变为一个平衡字符串,问替换子串的最小长度?
如果 s 已经平衡则输出0。
Input:
一行字符表示给定的字符串s
Output:
一个整数表示答案
examples:
Input:
QWER
Output:
0
Input:
QQWE
Output:
1
Input:
QQQW
Output:
2
Input:
QQQQ
Output:
3
个人思路:
刚看到这个题的时候,连暴力的做法都想不出来2333,不过,在学习过尺取法之后,就发现了新大陆!
尺取法:
顾名思义,像尺子一样取一段,借用挑战书上面的话说,尺取法通常是对数组保存一对下标,即所选取的区间的左右端点,然后根据实际情况不断地推进区间左右端点以得出答案。尺取法比直接暴力枚举区间效率高很多,尤其是数据量大的时候,所以说尺取法是一种高效的枚举区间的方法,是一种技巧,一般用于求取有一定限制的区间个数或最短的区间等等。当然任何技巧都存在其不足的地方,有些情况下尺取法不可行,无法得出正确答案,所以要先判断是否可以使用尺取法再进行计算。
好,看得出来尺取法的关键点是如何维护一对下标,具体来说,对于本题,因为其要求的是一段连续的区间,并且其左右端点都有明确的移动方向,所以才能使用尺取法。
假设下标为(L, R),那么对于本题,起始时L = R = 0,也就是数组的起点,
当[L, R]满足要求,则L++;
不满足要求,则R++;
对于此,下面关键就是什么是满足要求,什么是不满足。。
首先,我们需要记录Q、W、E、R,在L,R区间外的个数,也就是说,刚开始sumq,sumw,sume,sumr,就是整个数组中的个数,如果他们个数两两相等,那么就是满足的,直接return;
否则,R++,也就是往右移,计算出sumq,w,e,r,并且通过替换使得sumq,w,e,r相等,也就是 = MAX(sumq,w,e,r),然后看[L,R]中剩余的字符串数量是否是4的正倍数。
如果是,则L++,
不是则R++;
直到R < n;
代码实现:
#include<iostream>
#include<string>
#include<algorithm>
#include<map>
using namespace std;
string s;
int sum[4] = { 0 };
char ch[] = { 'Q','W','E','R' };
map<char, int>mp;
int result = 0;
//QWERQWERQWERQWERQQQQEEEE
int main() {
for (int i = 0; i < 4; ++i) {
mp[ch[i]] = i;
}
cin >> s;
int l = 0, r = 0;
int len = s.length();
for (int i = 0; i < len; ++i)
sum[mp[s[i]]]++;
if (sum[0] * 4 == len && sum[0] == sum[1] && sum[0] == sum[2] && sum[0] == sum[3]) {
cout << 0 << endl;
return 0;
}
result = len;
sum[mp[s[r]]]--;
while(r < len){
int t_char_num = r - l + 1;
int m = max(max(max(sum[0], sum[1]), sum[2]), sum[3]);
int remain = t_char_num - (m * 4 - sum[0] - sum[1] - sum[2] - sum[3]);//剩下的减去需要补的
if (remain >= 0 && remain % 4 == 0)
{
sum[mp[s[l]]]++;
result = min(r - l + 1, result);
if (r == l)
break;
l++;
}
else {
r++;
sum[mp[s[r]]]--;
}
}
cout << result << endl;
return 0;
}