LeetCode-995. K连续位的最小翻转次数(Minimum Number of K Consecutive Bit Flips)
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)\),其中 \(i\geq0\),特殊地,\(f(0) = a(0)\).
由定义我们可以得到如下性质:
- 性质1:\(a(x)=\sum\limits_{i=0}^{x} {f(i)}\),即\(a(x)\)等于\(f(x)\)的前缀和。
- 性质2:由性质1进一步可得\(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)}\)
- 性质3:对于区间\([L,R]\),将该区间每个元素都加上\(x\),由性质1我们知道,将\(f(L)+x\)后,后面所有的元素在计算前缀和时都会加上\(x\),因此我们只需要将\(f(R+1)-x\)就可以保证区间外的元素不会改变。
- 性质4:由性质2我们可以求得区间\([L,R]\)的元素和,即\(ans=sum(R)-sum(L-1)\)
介绍完差分数组,我们继续原题的解析。我们设差分数组diff
表示相邻两数字翻转次数差,设cnt
表示差分数组的累加值即当前数字翻转次数,我们易得以下推论:
推论1:对于当前元素\(a\),若\(a+cnt\)为偶数,则\(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\),这样当遍历到位置j
时,若有\(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;
}
};
-------------------------------------------
キミが笑うだけで 全回復だもん!
只要能看到你的笑容 就能完全恢复!
努力做一个灵魂有趣的人!
如果觉得这篇文章对你有小小的帮助的话,记得在右下角点个“推荐”哦,博主在此感谢!( ̄▽ ̄)~*
要是打赏一点就更好了(〜 ̄△ ̄)〜(前微信后支付宝)