995. K 连续位的最小翻转次数
在仅包含 0
和 1
的数组 A
中,一次 K
位翻转包括选择一个长度为 K
的(连续)子数组,同时将子数组中的每个 0
更改为 1
,而每个 1
更改为 0
。
返回所需的 K
位翻转的最小次数,以便数组没有值为 0
的元素。如果不可能,返回 -1
。
示例 1:
输入:A = [0,1,0], K = 1
输出:2
解释:先翻转 A[0],然后翻转 A[2]。
示例 2:
输入:A = [1,1,0], K = 2
输出:-1
解释:无论我们怎样翻转大小为 2 的子数组,我们都不能使数组变为 [1,1,1]。
示例 3:
输入:A = [0,0,0,1,0,1,1,0], K = 3
输出:3
解释:
翻转 A[0],A[1],A[2]: A变成 [1,1,1,1,0,1,1,0]
翻转 A[4],A[5],A[6]: A变成 [1,1,1,1,1,0,0,0]
翻转 A[5],A[6],A[7]: A变成 [1,1,1,1,1,1,1,1]
提示:
1 <= A.length <= 30000
1 <= K <= A.length
方法一:计数
解题思路
先思考暴力解法。无非是遍历数组,当 A[i] == 0
,则翻转 [i, i + K - 1]
区间的元素(在有效范围内),最后 K
位如果有 0
则返回 -1
。此方法时间复杂度为 O(n * K)
,超时~。
暴力法超时的原因有两点:
- 真实地进行了翻转。
- 对于
A[i]
翻转一次和翻转三次的效果是一样的。
在暴力法的基础上思考优化:
- 定义
int[] counts
数组,counts[i]
表示A[i]
需要翻转的次数。「翻转条件」为: counts[i] 为偶数且 A[i] 为 0 或 counts[i] 为奇数且 A[i] 为 1。 - 对于
A[i]
,翻转偶数次的效果一样,同理翻转奇数次的效果也一样。可以认为A[i]
要么翻转 0 次,要么翻转 1 次,所以counts[i]
非 0 即 1。我们可以优化上面的「翻转条件」为:counts[i] == A[i]
。 - 此时我们解决了多次翻转的问题,即把多次翻转改为不翻转或翻转一次。但仔细思考,我们只是把翻转子数组改成维护
counts
数组,时间复杂度依然是O(n * K)
。 - 进一步优化。需要翻转
[i, i + K - 1]
时,我们的做法是修改counts
数组角标[i, i + K - 1]
区间的值。换个思路,我们可以修改counts[i + K]
的值,表示这次翻转的影响到i + K
就结束了,并用变量curCount
表示当前的翻转状态。「翻转条件」进一步改为:curCount == A[i]
。
参考代码1
public int minKBitFlips(int[] A, int K) {
int n = A.length;
int[] counts = new int[n + 1];
int ret = 0, curCount = 0;
for (int i = 0; i < n; i++) {
curCount ^= counts[i];
if (curCount == A[i]) {
if (i + K > n) {
return -1;
}
curCount ^= 1;
counts[i + K] = 1;
ret++;
}
}
return ret;
}
- 时间复杂度:O(n);空间复杂度:O(n)
参考代码2
我们也可以直接使用参数 A
来代替 counts
的作用,把空间复杂度降为 O(1)。
public int minKBitFlips(int[] A, int K) {
int n = A.length;
int ret = 0, flag = 0;
for (int i = 0; i < n; i++) {
if (i >= K && A[i - K] > 1) {
flag ^= 1;
}
if (flag == A[i]) {
if (i + K > n) {
return -1;
}
flag ^= 1;
A[i] += 2;
ret++;
}
}
return ret;
}
- 时间复杂度:O(n);空间复杂度:O(1)
执行结果