K 连续位的最小翻转次数
在仅包含 0 和 1 的数组 A 中,一次 K 位翻转包括选择一个长度为 K 的(连续)子数组,同时将子数组中的每个 0 更改为 1,而每个 1 更改为 0。
返回所需的 K 位翻转的最小次数,以便数组没有值为 0 的元素。如果不可能,返回 -1。
输入:A = [0,1,0], K = 1
输出:2
解释:先翻转 A[0],然后翻转 A[2]。
输入:A = [1,1,0], K = 2
输出:-1
解释:无论我们怎样翻转大小为 2 的子数组,我们都不能使数组变为 [1,1,1]。
这一题的首先思路和贪心思想是很相似的,第一想到的点就是从最左边开始遍历整个数组,如果碰到0就可以翻转之后的K位数字,如果到了[ len-K, len] 的范围内还碰到有0的话,就判断无法满足条件。
但是如果用暴力的方法来写的话时间复杂度是O(nK)而造成超时,所以要用一种巧妙的方法来记录某一位到底反转了几次。
首先对于某一位上的数字,只有0,1两种可能性。所以只有数字反转了奇数次才是有意义的。
那么我们知道题目要求一次是翻转窗口K大小的子数组。我们可以用
差
分
数
组
差分数组
差分数组的方式来记录当前数字的翻转次数。
我们构造差分数组 reverse[N+1] ,并且用ans来代表总共进行翻转操作的次数,用CNT代表当前数字翻转的次数。差分的意思是 reverse[i] 代表了原数组A[i-1] 和 A[i] 两位数字的翻转次数之差。换句话说,如果我们在第 p位上检测到了0,那么对于 A[p:p+K]的范围内的数字也全部会进行翻转,但是在这个范围内的数字之间的翻转次数是相同的,在这个情况下是 A[p] 这个数字比 A[p-1] 多翻转了一次, A[p+K] 比 A[p+K-1] 少翻转了一次。所以我们需要在差分矩阵 reverse[p+K] 减少1; reverse[p] 增加1 (这个操作是通过CNT记录的)。
这样我们通过CNT和reverse对应位置上的累加即可求出A 每一位数字的翻转次数。
遍历到 A[i] 时,若A[i]+CNT 是偶数,则代表这里需要进行翻转,则翻转A[i:i+K-1],在这里直接将CNT++, reverse[i+K]-- 即可。
注意到若 i+K>n 则无法执行翻转操作,此时应返回 -1−1。
我们来看第一个例子:
A = [0,1,0] K=2
首先构造reverse = [0,0,0,0] CNT=0 ans=0
对于 i=0 ,首先判断A[i] + CNT,发现是0,则需要翻转A[0], A[1]。于是 当前的翻转次数CNT=1, ans=1。在这里CNT代表的是第0~1位的数字翻转了1次,但是第2位则不需要翻转,所以将差分矩阵reverse[2] – 为[0,0,-1,0]
对于i=1, 首先CNT+=reverse[i] = 1,代表当前数字已经被翻转了一次。再判断A[i]+CNT,发现为0,因为本来第二个数字是1,然后又翻转了一次,所以这里就变成0了。于是需要翻转1,2位,当前翻转次数位CNT=2, ans=2。更新差分矩阵reverse[3]-- ,为 [0,0,-1,-1]
对于i=2, 首先更新CNT + reverse[i] = 1,代表第二位也翻转了一次,再判断A[i]+CNT 发现为1,是奇数,就代表这里已经是1了,不需要翻转。到此判断结束,返回2。
这个题目感觉有点难懂,但是理解差分数组之后确实会很通顺的做出来。
C++ 代码如下
class Solution {
public:
int minKBitFlips(vector<int>& A, int K) {
int N = A.size();
int count = 0;
vector<int> rev(N+1);
int cur =0;
for (int i=0;i<N;i++)
{
cur += rev[i];
if ((A[i]+cur)%2==0)
{
if (i+K>N) return -1;
cur++;
count++;
rev[K+i]--;
}
}
return count;
}
};