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 ) f(i) = a(i) - a(i-1) f(i)=a(i)a(i1),其中 i ≥ 0 i\geq0 i0,特殊地, 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=0xf(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=0xa(i)=i=0xj=0if(j)=i=0x(xi+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(L1)

介绍完差分数组,我们继续原题的解析。我们设差分数组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[jK]>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;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值