## 题目地址(995. K 连续位的最小翻转次数)

https://leetcode-cn.com/problems/minimum-number-of-k-consecutive-bit-flips/

## 题目描述

```
在仅包含 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
```

## 前置知识

- 连续子数组优化

## 公司

- 暂无

## 暴力解

### 思路

首先考虑暴力的解法。暴力的思路可以是从左到右遍历数组，如果碰到一个 0，我们以其为左端进行翻转。翻转的长度自然是以其开始长度为 K 的子数组了。由于是**以其为左端进行翻转**，因此如果遇到一个 0 ，我们必须执行翻转，否则就无法得到全 1 数组。由于翻转的顺序不影响最终结果，即如果最终答案是翻转以 i, j , k 为起点的子数组，那么先翻转谁后翻转谁都是一样的。因此采用从左往右遍历的方式是可以的。

概括一下：暴力的思路可以是从左到右遍历数组，如果碰到一个 0，我们以其为左端进行翻转，并修改当前位置开始的长度为 k 的子数组，同时计数器 + 1，最终如果数组不全为 0 则返回 -1 ，否则返回计数器的值。

### 代码

- 语言支持：Python3

Python3 Code:

```py
class Solution:
    def minKBitFlips(self, A, K):
        N = len(A)
        ans = 0
        for i in range(N - K + 1):
            if A[i] == 1:
                continue
            for j in range(K):
                A[i + j] ^= 1
            ans += 1
        for i in range(N):
            if A[i] == 0:
                return -1
        return ans
```

**复杂度分析**

令 n 为数组长度。

- 时间复杂度：$O(n * k)$
- 空间复杂度：$O(1)$

## 连续子数组优化

### 思路

对于这种连续子数组的题目。一般优化思路就那么几种。我们来枚举一下：

- [前缀和 & 差分数组](https://github.com/azl397985856/leetcode/blob/master/selected/atMostK.md)
- [滑动窗口](https://github.com/azl397985856/leetcode/blob/master/thinkings/slide-window.md)
- 双端队列。比如 1696. 跳跃游戏 VI 和 239. 滑动窗口最大值 就是这种思路。

这三种技巧我都写过文章，如果不了解可以先看下。

对于这道题来说，我们可使用差分数组或者双端队列来优化。不管采用哪种，基本思路都差不多，你也可以对比下方代码看一下他们思路的一致性。 简单来说，他们的思路差不多，差别只是解决问题的使用的数据结构不同，因此 api 不同罢了。因此我并没有将二者作为两个解法。

对于差分数组来说，上面暴力解法内层有一个次数为 k 的循环。而如果使用差分数组只修改端点的值，就可轻松将时间复杂度优化到 $O(n)$。

对于双端队列来说，如果当前位置需要翻转，那么就将其入队。那如何判断当前位置的数字是多少呢？（可能由于前面数字的翻转，当前位置**被**翻转了若干次，可能不是以前的数字了）。由于被翻转偶数次等于没有翻转，被翻转奇数次效果一样，因此只需要记录被翻转次数的奇偶性即可。而这其实就是队列长度的奇偶性。因为**此时队列的长度就是当前数字被翻转的次数**。当然这要求你将长度已经大于 k 的从队列中移除。

### 关键点

- 连续子数组优化技巧

### 代码

- 语言支持：Python3

Python3 Code:

差分数组：

```py

class Solution:
    def minKBitFlips(self, A: List[int], K: int) -> int:
        n = len(A)
        diff = [0] * (n + 1)
        ans, cur = 0, 0
        for i in range(n):
            cur += diff[i]
            if cur % 2 == A[i]:
                if i + K > n:
                    return -1
                ans += 1
                cur += 1
                diff[i + K] -= 1
        return ans

```

**复杂度分析**

令 n 为数组长度。

- 时间复杂度：$O(n)$
- 空间复杂度：$O(n)$

双端队列：

```py
class Solution:
    def minKBitFlips(self, A, K):
        N = len(A)
        q = collections.deque()
        ans = 0
        for i in range(N):
            if q and i >= q[0] + K:
                q.popleft()
            if len(q) % 2 == A[i]:
                if i + K > N:
                    return -1
                q.append(i)
                ans += 1
        return ans
```

**复杂度分析**

令 n 为数组长度。

- 时间复杂度：$O(n)$
- 空间复杂度：$O(k)$

## 空间复杂度为 O(1) 的算法

### 思路

不管是使用差分数组和还是双端队列，其实都没有必要使用额外的数据结构，而是使用原来的数组 A，也就是**原地修改**。

比如，我们可以只记录双端队列的长度，要判断一个数字是否在队列里，只需要将其映射到题目数据范围内的另外一个数字即可。由于题目数据范围是 [0,1] 因此我们可以将其映射到 2。

### 关键点

- 原地修改

### 代码

- 语言支持：Python3

Python3 Code:

```py

class Solution:
    def minKBitFlips(self, A, K):
        flips = ans = 0
        for i in range(len(A)):
            if i >= K and A[i - K] - 2 == 0:
                flips -= 1
            if (flips % 2) == A[i]:
                if i + K > len(A):
                    return -1
                A[i] = 2
                flips += 1
                ans += 1
        return ans

```

**复杂度分析**

令 n 为数组长度。

- 时间复杂度：$O(n)$
- 空间复杂度：$O(1)$

> 此题解由 [力扣刷题插件](https://leetcode-pp.github.io/leetcode-cheat/?tab=solution-template) 自动生成。

力扣的小伙伴可以[关注我](https://leetcode-cn.com/u/fe-lucifer/)，这样就会第一时间收到我的动态啦~

以上就是本文的全部内容了。大家对此有何看法，欢迎给我留言，我有时间都会一一查看回答。更多算法套路可以访问我的 LeetCode 题解仓库：https://github.com/azl397985856/leetcode 。 目前已经 40K star 啦。大家也可以关注我的公众号《力扣加加》带你啃下算法这块硬骨头。

关注公众号力扣加加，努力用清晰直白的语言还原解题思路，并且有大量图解，手把手教你识别套路，高效刷题。

![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfcuzagjalj30p00dwabs.jpg)
