LeetCode-995. K连续位的最小翻转次数(Minimum Number of K Consecutive Bit Flips)

K连续位的最小翻转次数

在仅包含 01 的数组 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;
    }
};
posted @ 2021-02-18 11:34  _Roki  阅读(113)  评论(0编辑  收藏  举报