双指针 滑动窗口 经典问题 : 选择一个尽可能长的区间,使得区间中恰好包含 k 个 0,把这些 0 染成 1 得到最长1串
https://ac.nowcoder.com/acm/contest/108576/E
给定一个长度为 n 的 01 串,其中 1 表示白色,0 表示黑色。现在需要对这个字符串进行恰好 k 次染色操作,每次染色操作必须选择之前未被选择过的位置,将该位置的颜色取反(即白色变黑,黑色变白)。染色完成后,求最长连续纯色子串的长度(即连续的一段字符,所有字符颜色相同)。
解题思路
我们注意到最终的最长纯色子串要么全是 0,要么全是 1。因此我们可以分别考虑两种情况:
以全 1 为目标的最长子串长度。
以全 0 为目标的最长子串长度。
我们可以通过翻转字符串(0 变 1,1 变 0)来将求全 0 子串的问题转化为求全 1 子串的问题。
核心思路:
我们只需实现一个函数 work(s),计算在翻转恰好 k 次后,字符串 s 中最长的全 1 子串长度。然后分别对原字符串和翻转后的字符串调用 work,取最大值即可。
实现 work(s) 函数:
假设当前字符串为 s,我们要计算翻转恰好 k 次后,最长的全 1 子串长度。
统计字符数量:
设字符串中 0 的数量为 X,1 的数量为 Y。
分情况讨论:
情况1:当 k >= X 时:
我们可以将所有 0 翻转为 1,此时还剩下 k - X 次翻转。
此时整个字符串已经全是 1,我们需要用剩下的 k - X 次翻转,将一些 1 翻转回 0。
为了使得最长全 1 子串尽可能长,我们应当尽量少地翻转 1 为 0。
因此,我们最多允许翻转 k - X 个 1 为 0,即最长全 1 子串最多包含 Y - (k - X) 个原始 1。
问题转化为:在字符串中找到最长的连续子串,其原始 1 的数量恰好为 Y - (k - X)。
使用滑动窗口(双指针)解决。
情况2:当 k < X 时:
我们无法将所有 0 翻转为 1,只能翻转其中的 k 个 0 为 1。
为了使最长全 1 子串尽可能长,我们应当选择一段连续的子串,其中恰好包含 k 个 0(翻转后全部变成 1),其余字符均为 1。
问题转化为:在字符串中找到最长的连续子串,其原始 0 的数量恰好为 k。
使用滑动窗口(双指针)解决。
Solution Code:
void solve() {
int n, K;
string s;
cin >> n >> K >> s;
auto work = [&](string s) -> int {
s = " " + s;
int X = 0, Y = 0, k = K;
for(int i = 1; i <= n; i++) {
X += (s[i] == '0');
Y += (s[i] == '1');
}
int ans = 0;
if(k > X) {
k -= X;
Y -= k;
// cerr << "Y : " << Y << ", k : " << k << endl;
int cnt = 0;
for(int i = 1, j = 1; i <= n; i++) {
cnt += (s[i] == '1');
while(cnt > Y && j <= i) {
if(s[j] == '1') {
cnt -- ;
}
j ++ ;
}
if(cnt == Y && j <= i) {
ans = max(ans, i - j + 1);
}
}
return ans;
}
X = 0;
for(int i = 1, j = 1; i <= n; i++) {
X += (s[i] == '0');
while(X > k && j <= i) {
if(s[j] == '0') {
X -- ;
}
j ++ ;
}
if(j <= i && X == k) {
ans = max(ans, i - j + 1);
}
}
return ans;
};
int ans = work(s);
for(int i = 0; i < n; i++) { // flip the string
s[i] ^= 1;
}
ans = max(ans, work(s));
cout << ans << endl;
}
浙公网安备 33010602011771号