【力扣】995. K 连续位的最小翻转次数

以下为力扣的官方题解

题目

在仅包含 0 0 0 1 1 1 的数组 A A A 中,一次 K K K 位翻转包括选择一个长度为 K K K 的(连续)子数组,同时将子数组中的每个 0 0 0 更改为 1 1 1,而每个 1 1 1 更改为 0。
返回所需的 K K K 位翻转的最小次数,以便数组没有值为 0 0 0 的元素。如果不可能,返回 − 1 -1 1

示例1

输入 A = [ 0 , 1 , 0 ] , K = 1 A = [0,1,0], K = 1 A=[0,1,0],K=1
输出 2 2 2
解释:先翻转 A [ 0 ] A[0] A[0],然后翻转 A [ 2 ] A[2] A[2]

示例2

输入 A = [ 1 , 1 , 0 ] , K = 2 A = [1,1,0], K = 2 A=[1,1,0],K=2
输出 − 1 -1 1
解释:无论我们怎样翻转大小为 2 2 2 的子数组,我们都不能使数组变为 [ 1 , 1 , 1 ] [1,1,1] [1,1,1]

示例3

输入 A = [ 0 , 0 , 0 , 1 , 0 , 1 , 1 , 0 ] , K = 3 A = [0,0,0,1,0,1,1,0], K = 3 A=[0,0,0,1,0,1,1,0],K=3
输出 3 3 3
解释
翻转 A [ 0 ] , A [ 1 ] , A [ 2 ] : A[0],A[1],A[2]: A[0],A[1],A[2]: A A A 变成 [ 1 , 1 , 1 , 1 , 0 , 1 , 1 , 0 ] [1,1,1,1,0,1,1,0] [1,1,1,1,0,1,1,0]
翻转 A [ 4 ] , A [ 5 ] , A [ 6 ] : A[4],A[5],A[6]: A[4],A[5],A[6]: A A A 变成 [ 1 , 1 , 1 , 1 , 1 , 0 , 0 , 0 ] [1,1,1,1,1,0,0,0] [1,1,1,1,1,0,0,0]
翻转 A [ 5 ] , A [ 6 ] , A [ 7 ] : A[5],A[6],A[7]: A[5],A[6],A[7]: A A A 变成 [ 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ] [1,1,1,1,1,1,1,1] [1,1,1,1,1,1,1,1]

提示

  1. 1 < = A . l e n g t h < = 30000 1 <= A.length <= 30000 1<=A.length<=30000
  2. 1 < = K < = A . l e n g t h 1 <= K <= A.length 1<=K<=A.length

官方题解

方法一 差分数组

由于对同一个子数组执行两次翻转操作不会改变该子数组,所以对每个长度为 K K K 的子数组,应该至多执行一次翻转操作。

对于若干个 K K K 位翻转操作,改变先后顺序并不影响翻转的结果。不妨从 A [ 0 ] A[0] A[0] 开始考虑,若 A [ 0 ] = 0 A[0] = 0 A[0]=0,则必定要翻转从位置 0 0 0 开始的子数组;若 A [ 0 ] = 1 A[0] = 1 A[0]=1,则不翻转从位置 0 0 0 开始的子数组。

按照这一策略,我们从左到右地执行这些翻转操作。由于翻转操作是唯一的,若最终数组元素均为 1 1 1,则执行的翻转次数就是最小的。

N N N 表示数组 A A A 的长度。若直接模拟上述过程,复杂度将会是 O ( N K ) O(NK) O(NK)。如何优化呢?

考虑不去翻转数字,而是统计每个数字需要翻转的次数。对于一次翻转操作,相当于把子数组中所有数字的翻转次数加 1 1 1

这启发我们用差分数组的思想来计算当前数字需要翻转的次数。我们可以维护一个差分数组 d i f f diff diff,其中 d i f f [ i ] diff[i] diff[i] 表示两个相邻元素 A [ i − 1 ] A[i-1] A[i1] A [ i ] A[i] A[i] 的翻转次数的差,对于区间 [ l , r ] [l, r] [l,r],将其元素全部加 1 1 1,只会影响到 l l l r + 1 r+1 r+1 处的差分值,故 d i f f [ l ] diff[l] diff[l] 增加 1 1 1 d i f f [ r + 1 ] diff[r+1] diff[r+1] 减少 1 1 1

通过累加差分数组可以得到当前位置需要翻转的次数,我们用变量 r e v C n t revCnt revCnt 来表示这一累加值。

遍历到 A [ i ] A[i] A[i] 时,若 A [ i ] + r e v C n t A[i]+revCnt A[i]+revCnt 是偶数,则说明当前元素的实际值为 0 0 0,需要翻转区间 [ i , i + K − 1 ] [i, i+K-1] [i,i+K1],我们可以直接将 r e v C n t revCnt revCnt 增加 1 1 1 d i f f [ i + K ] diff[i+K] diff[i+K] 减少 1 1 1

注意到若 i + K > n i+K \gt n i+K>n,则无法执行翻转操作,此时应返回 − 1 -1 1

代码

class Solution {
    public int minKBitFlips(int[] A, int K) {
        int n = A.length;
        int[] diff = new int[n+1];
        int ans = 0, revCnt = 0;
        for (int i=0; i<n; i++)
        {
            revCnt += diff[i];
            if ((A[i]+revCnt) % 2 == 0)
            {
                if (i+K > n)
                    return -1;
                
                ans ++;
                revCnt ++;
                diff[i+K] --;
            }
        }
        return ans;
    }
}

由于模 2 2 2 意义下的加减法与异或等价,我们也可以用异或改写上面的代码。

class Solution {
    public int minKBitFlips(int[] A, int K) {
        int n = A.length;
        int[] diff = new int[n+1];
        int ans = 0, revCnt = 0;
        for (int i=0; i<n; i++)
        {
            revCnt ^= diff[i];
            if (A[i] == revCnt)     // A[i] ^ revCnt == 0
            {
                if (i+K > n)
                    return -1;

                ans ++;
                revCnt ^= 1;
                diff[i+K] ^=1;
            }
        }
        return ans;
    }
}

复杂度分析

  • 时间复杂度: O ( N ) O(N) O(N),其中 N N N 是数组 A A A 的长度。需要对数组 A A A 遍历一次。
  • 空间复杂度: O ( N ) O(N) O(N),其中 N N N 是数组 A A A 的长度。需要创建一个长度为 N + 1 N+1 N+1 的差分数组 d i f f diff diff

方法二 滑动窗口

能否将空间复杂度优化至 O ( 1 ) O(1) O(1) 呢?

回顾方法一的代码,当遍历到位置 i i i 时,若能知道位置 i − K i-K iK 上发生了翻转操作,便可以直接修改 r e v C n t revCnt revCnt,从而去掉 d i f f diff diff 数组。

注意到 0 ≤ A [ i ] ≤ 1 0 \leq A[i] \leq 1 0A[i]1,我们可以用 A [ i ] A[i] A[i] 范围之外的数来表达「是否翻转过」的含义。

具体来说,若要翻转从位置 i i i 开始的子数组,可以将 A [ i ] A[i] A[i] 2 2 2,这样当遍历到位置 i ′ i' i 时,若有 A [ i ′ − K ] > 1 A[i'-K] \gt 1 A[iK]>1,则说明在位置 i ′ − K i'-K iK 上发生了翻转操作。

代码

class Solution {
    public int minKBitFlips(int[] A, int K) {
        int n = A.length;
        int ans = 0, revCnt = 0;
        for (int i=0; i<n; i++)
        {
            if (i>=K && A[i-K]>1)
            {
                revCnt ^= 1;
                A[i-K] -= 2;    // 复原数组元素,若允许修改数组 A,则可以省略
            }
            if (A[i] == revCnt)
            {
                if (i+K > n)
                    return -1;

                ans ++;
                revCnt ^= 1;
                A[i] +=2;
            }
        }
        return ans;
    }
}

复杂度分析

  • 时间复杂度: O ( N ) O(N) O(N)。其中 N N N 是数组 A A A 的长度,需要对数组 A A A 遍历一次。
  • 空间复杂度: O ( 1 ) O(1) O(1)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值