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
Minimum Number of K Consecutive Bit Flips
In an array A
containing only 0s and 1s, a K
-bit flip consists of choosing a (contiguous) subarray of length K
and simultaneously changing every 0 in the subarray to 1, and every 1 in the subarray to 0.
Return the minimum number of K
-bit flips required so that there is no 0 in the array. If it is not possible, return -1
.
Example 1:
Input: A = [0,1,0], K = 1
Output: 2
Explanation: Flip A[0], then flip A[2].
Example 2:
Input: A = [1,1,0], K = 2
Output: -1
Explanation: No matter how we flip subarrays of size 2, we can’t make the array become [1,1,1].
Example 3:
Input: A = [0,0,0,1,0,1,1,0], K = 3
Output: 3
Explanation:
Flip A[0],A[1],A[2]: A becomes [1,1,1,1,0,1,1,0]
Flip A[4],A[5],A[6]: A becomes [1,1,1,1,1,0,0,0]
Flip A[5],A[6],A[7]: A becomes [1,1,1,1,1,1,1,1]
Note:
1 <= A.length <= 30000
1 <= K <= A.length
解
引理1. 当数组有解时,从头开始,遇到0就翻转,这种解法是最优的。
证明:我们假设前i-1
个元素都是1,对于第i
个元素有以下情况:
- 第
i
个元素为1时,我们将其移除窗口即不对其进行翻转操作,这是代价最小的操作。 - 第
i
个元素为0时,要将其变成1,则需要奇数次翻转,因为前i-1
个元素都是1,第i
个元素一定在窗口头部,因此将其翻转一次并将窗口后移一位,是代价最小的操作。
下面介绍的方法都是基于引理1设计的。
方法一:差分数组
如果我们直接模拟引理1的方法,则会有较大空间开销,因此我们使用差分数组。
定义:对于一个数组
a
来说,设其差分数组f
,则有 f ( i ) = a ( i ) − a ( i − 1 ) f(i) = a(i) - a(i-1) f(i)=a(i)−a(i−1),其中 i ≥ 0 i\geq0 i≥0,特殊地, f ( 0 ) = a ( 0 ) f(0) = a(0) f(0)=a(0).
由定义我们可以得到如下性质:
- 性质1: a ( x ) = ∑ i = 0 x f ( i ) a(x)=\sum\limits_{i=0}^{x} {f(i)} a(x)=i=0∑xf(i),即 a ( x ) a(x) a(x)等于 f ( x ) f(x) f(x)的前缀和。
- 性质2:由性质1进一步可得 s u m ( x ) = ∑ i = 0 x a ( i ) = ∑ i = 0 x ∑ j = 0 i f ( j ) = ∑ i = 0 x ( x − i + 1 ) f ( j ) sum(x)=\sum\limits_{i=0}^{x} {a(i)}=\sum\limits_{i=0}^{x} {\sum\limits_{j=0}^{i} {f(j)}}=\sum\limits_{i=0}^{x}{(x-i+1)f(j)} sum(x)=i=0∑xa(i)=i=0∑xj=0∑if(j)=i=0∑x(x−i+1)f(j)
- 性质3:对于区间 [ L , R ] [L,R] [L,R],将该区间每个元素都加上 x x x,由性质1我们知道,将 f ( L ) + x f(L)+x f(L)+x后,后面所有的元素在计算前缀和时都会加上 x x x,因此我们只需要将 f ( R + 1 ) − x f(R+1)-x f(R+1)−x就可以保证区间外的元素不会改变。
- 性质4:由性质2我们可以求得区间 [ L , R ] [L,R] [L,R]的元素和,即 a n s = s u m ( R ) − s u m ( L − 1 ) ans=sum(R)-sum(L-1) ans=sum(R)−sum(L−1)
介绍完差分数组,我们继续原题的解析。我们设差分数组diff
表示相邻两数字翻转次数差,设cnt
表示差分数组的累加值即当前数字翻转次数,我们易得以下推论:
推论1:对于当前元素 a a a,若 a + c n t a+cnt a+cnt为偶数,则 a = 0 a=0 a=0
我们翻转区间时只需要运用性质3即可。
以下是代码:
class Solution {
public:
int minKBitFlips(vector<int>& A, int K) {
int n = A.size();
vector<int> diff(n + 1);
int ret = 0, cnt = 0;
for (int i = 0; i < n; ++i) {
cnt += diff[i];
if ((A[i] + cnt) % 2 == 0) {
if (i + K > n) {
return -1;
}
ret++;
diff[i + 1]++;
diff[i + K]--;
}
}
return ret;
}
};
方法二:滑动窗口
在方法一中,我们将记录翻转次数的数据优化成差分数组,在此基础上,思考:是否可以将差分数组优化掉?
我们注意到,原数组取值为0或1,那么我们可以用范围外的数来标志该数是否被翻转。具体做法为:
若要翻转从位置i
开始的子数组,可以将
A
[
i
]
+
2
A[i]+2
A[i]+2,这样当遍历到位置j
时,若有
A
[
j
−
K
]
>
1
A[j-K]>1
A[j−K]>1则说明在位置j-K
上发生了翻转操作。
同时,我们可以将mod 2
的操作变成异或操作。
如下:
class Solution {
public:
int minKBitFlips(vector<int>& A, int K) {
int n = A.size();
int ret = 0, cnt = 0;
for (int i = 0; i < n; ++i) {
if (i >= K && A[i - K] > 1) {
cnt ^= 1;
A[i - K] -= 2;
}
if (A[i] == cnt) { // A[i] ^ revCnt == 0
if (i + K > n) {
return -1;
}
ret++;
cnt ^= 1;
A[i] += 2;
}
}
return ret;
}
};