目录
题目:
题目描述:
对于每一个案例,给你一个 01字符串,你可以从前或者从后删除一些字符串。这样操作的成本是max( 保留部分中 0 的个数 ,删除部分中 1 的个数 )。问最小的成本是多少?
思路(双指针):
对于双指针,我们最重要的思路就是找到一种单调性。
为了简化表述,我们先不妨设
c0:保留下来的 0 的个数
c1:删除掉的 1 的个数
左指针:保留区间左边界
右指针:保留区间右边界
目标:找到 max( c0 ,c1)的最小值
如果基于花销进行双指针,挪了左指针有可能花销上升,也可能下降,挪右指针花销也可能上升,也可能下降...此时没有单调性。
但是我们换一种思路,基于 c0 和 c1 的个数进行双指针,如果我们右移右指针(即扩增保留区间)的时候:
①如果加入到保留区间的是 0 ,那么 c0 增多(保留下来的 0 的个数增多), c1 不变(删除掉的 1 的个数不变)
②如果加入到保留区间的是 1 ,那么 c1 减少(删除掉的 1 的个数减少), c0 不变(保留下来的 0 的个数不变)
我们发现,对于扩增来说, c0 永远是单调递增的, c1 永远是单调递减的,在同一个左指针下, c0 和 c1 的变化感觉大概如下图所示:
并且我们还可以看到, c0 和 c1 并不会同时增加或者减少,一定是一个变化而另一个不变,那么说明在同一个左指针下,我们右移右指针的过程中,必然不会错过最佳位置 c0 和 c1 相等的时刻,也就是在这个左指针下 max( c0 ,c1)能找到的最小值。
在图中我们也可以看出,在右指针移动到了 c0 和 c1 相等的位置(交点)之后,就没有必要再右移了,如果再右移找到的 max( c0 ,c1)只会比之前的大。
在左指针右移的时候, c0 只会递减, c1 只会递增,且两者必一个变化而另一个不动。说明只会让 c1 变的更大或 c0 变得更小,从而一定拉大二者差距,所以对于新的左指针,右指针无需重置,继续往右走即可找到本轮的最小值。
简单来说,就是在左右指针移动的时候,动态维护 c0 , c1 ,以此找到所有可能具有最小值的区间
思路有了,具体操作请看AC代码:
AC代码(双指针):
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 5;
const int inf = 2e9 + 5;
char s[N];
int main()
{
std::ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
cin >> t;
while (t--)
{
int ans = inf;
int c0 = 0;//保留的0的个数
int c1 = 0;//删去的1的个数
cin >> s + 1;
int n = strlen(s + 1);
for (int i = 1; i <= n; i++)
if (s[i] == '1')
c1++;
for (int i = 1, j = 0; i <= n; i++)
{
while (j + 1 <= n && c1 > c0)
{
j++;
if (s[j] == '0')
c0++;
else
c1--;
}
ans = min(ans, max(c0, c1));
if (s[i] == '0')
c0--;
else
c1++;
}
cout << ans << '\n';
}
return 0;
}
思路(推导):
为了简化表述,我们先不妨设
b:保留下来的所有0和1个数
b0:保留下来的 0 的个数
b1:保留下来的 1 的个数
s0:删除部分的 0 的个数
s1:删除部分的 1 的个数
总0:01串中所有的 0 的个数
总1:01串中所有的 1 的个数
目标:找到 max( 保留部分中 0 的个数 ,删除部分中 1 的个数 )的最小值
我们对目标进行一个推导和转化:
原:max( 保留部分中 0 的个数 ,删除部分中 1 的个数 )
= max(b0, s1)
= max(b0,总1 - b1)
= max(b0,总1 - b1) + b1 - b1
= max(b0 + b1,总1 ) - b1
= max(b,总1)- b1
推导到这里,似乎推不动了,我们进行分类讨论,探查什么时候可能会有最小值:
①当 b >= 总1 时:
min { max(b,总1)- b1 }
= min{ b - b1 }
所以为了让b更接近b1,所以当 b == 总1 时,min{ b - b1 } 有最小值
②当 b <= 总1 时:
min { max(b,总1)- b1 }
= min{ 总1 - b1 }
为了让b1更接近总1,所以当 b == 总1 时,min{ 总1 - b1 } 有最小值
综上,两类都有共同的结论,当 b == 总1 时,有最小值,那么我们接着推:
max(b,总1)- b1
= max(总1,总1)- b1
= 总1 - b1
因为 b == 总1 ,即(保留的长度 == 总1个数 )所以我们现在的目标转换成了,探查所有连续长度为总1的序列中,找到最小的(总1 - b1)
具体实现我们可以用前缀和,就不再详细展开了。
思路有了,具体操作请看AC代码
AC代码(推导):
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 5;
const int inf = 2e9 + 5;
int a[N];//代表制从0~i位置总共有多少个1
char s[N];
int main()
{
std::ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t; cin >> t;
while (t--)
{
memset(a, 0, sizeof(a));
cin >> s + 1;
int n = strlen(s + 1);
for (int i = 1; i <= n; i++)
a[i] = a[i - 1] + (s[i] == '1');
int m = a[n];//a[n]代表着总1的个数
for (int i = a[n]; i <= n; i++)
m = min(m, a[n] - (a[i] - a[i - a[n]]));
//上面一行第一个出现的a[n]代表着“总1个数”
//第二个出现的a[n]代表着“大小为总1个数的长度”
cout << m << '\n';
}
return 0;
}