|
| 1 | +## 题目地址(1004. 最大连续 1 的个数 III) |
| 2 | + |
| 3 | +https://leetcode-cn.com/problems/max-consecutive-ones-iii/ |
| 4 | + |
| 5 | +## 题目描述 |
| 6 | + |
| 7 | +``` |
| 8 | +给定一个由若干 0 和 1 组成的数组 A,我们最多可以将 K 个值从 0 变成 1 。 |
| 9 | +
|
| 10 | +返回仅包含 1 的最长(连续)子数组的长度。 |
| 11 | +
|
| 12 | + |
| 13 | +
|
| 14 | +示例 1: |
| 15 | +
|
| 16 | +输入:A = [1,1,1,0,0,0,1,1,1,1,0], K = 2 |
| 17 | +输出:6 |
| 18 | +解释: |
| 19 | +[1,1,1,0,0,1,1,1,1,1,1] |
| 20 | +粗体数字从 0 翻转到 1,最长的子数组长度为 6。 |
| 21 | +
|
| 22 | +示例 2: |
| 23 | +
|
| 24 | +输入:A = [0,0,1,1,0,0,1,1,1,0,1,1,0,0,0,1,1,1,1], K = 3 |
| 25 | +输出:10 |
| 26 | +解释: |
| 27 | +[0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1,1] |
| 28 | +粗体数字从 0 翻转到 1,最长的子数组长度为 10。 |
| 29 | +
|
| 30 | + |
| 31 | +
|
| 32 | +提示: |
| 33 | +
|
| 34 | +1 <= A.length <= 20000 |
| 35 | +0 <= K <= A.length |
| 36 | +A[i] 为 0 或 1 |
| 37 | +``` |
| 38 | + |
| 39 | +## 前置知识 |
| 40 | + |
| 41 | +- |
| 42 | + |
| 43 | +## 公司 |
| 44 | + |
| 45 | +- 暂无 |
| 46 | + |
| 47 | +## 思路 |
| 48 | + |
| 49 | +这道题我在 [字节跳动的算法面试题是什么难度?](https://lucifer.ren/blog/2020/09/06/byte-dance-algo-ex/) 提到过这道题的换皮题。大家可以将这两道题目结合起来理解。接下来,我们看下这道题如何解决。 |
| 50 | + |
| 51 | +如果题目没有`最多可以将 K 个值从 0 变成 1` 。这个条件,那么会很简单,是一个常规的滑动窗口模板题。 我们加上这个条件对问题有什么样的影响呢? |
| 52 | + |
| 53 | +这道题我们只需要记录下加入窗口的是 0 还是 1: |
| 54 | + |
| 55 | +- 如果是 1,我们什么都不用做 |
| 56 | +- 如果是 0,我们将 K 减 1 |
| 57 | + |
| 58 | +相应地,我们需要记录移除窗口的是 0 还是 1: |
| 59 | + |
| 60 | +- 如果是 1,我们什么都不做 |
| 61 | +- 如果是 0,说明加进来的时候就是 1,加进来的时候我们 K 减去了 1,这个时候我们再加 1。 |
| 62 | + |
| 63 | +### 为什么这种思路可行? |
| 64 | + |
| 65 | +这其实就是滑动窗口的常见套路。 这种算法可行的根本原因在于其本身就是暴力枚举的优化。如果让你用最暴力的解法如何求解呢?无非就是枚举所有的子数组,这需要 $O(N^2)$ 的时间复杂度,接下来判断子数组是否满足**最多将 k 个 0 变成 1,子数组全部为 1**。如果满足则更新答案即可。 如何对暴力解进行优化呢? |
| 66 | + |
| 67 | +比如现在是判断的子数组 A[2:3],我们计算出 A[2:3] 有一个 0,也就是说需要将一个 0 变成 1 才行。那么当我们继续判断子数组 A[2:4],我们只需要判断 A[4],同时结合 A[2:3]的计数信息即可。**滑动窗口就是专门优化这种每次只在端点变化,中间都不变,从而省去了中间即重复计算,进而将窗口内的计数信息从 O(w) 降低到 O(1)**,其中 w 为窗口大小。 |
| 68 | + |
| 69 | +接下来,我们继续优化。实际上也是没有必要两层循环枚举所有子数组的。而是在 $O(n)$ 的时间就可以枚举所有的合法子数组。为什么呢?你可以换个角度思考: |
| 70 | + |
| 71 | +所有的子数组就是 |
| 72 | + |
| 73 | +- 以索引 0 为右端点的所有子数组 |
| 74 | +- - 以索引 1 为右端点的所有子数组 |
| 75 | +- - 以索引 2 为右端点的所有子数组 |
| 76 | +- ... |
| 77 | +- - 以索引 n - 1 为右端点的所有子数组,其中 n 为数组长度 |
| 78 | + |
| 79 | +这样的话我们就可以使用双指针技巧。右指针模拟右端点,使用左指针模拟左端点了。**如果以索引 i 为右端点的子数组 0 的个数不大于 k,那么左指针 l 没必要右移,因为当前右指针 r 和所有的索引 i, 其中 l <= i <= r 的组合 0 的个数都不会大于 k,而且子数组还更短了,不可能是答案,因此我们直接右移右指针,这是算法的关键**。通过这种方式算法的时间复杂度可从 $O(n^2)$ 降低到 $n$。 |
| 80 | + |
| 81 | +## 代码 |
| 82 | + |
| 83 | +- 语言支持:Python3 |
| 84 | + |
| 85 | +Python3 Code: |
| 86 | + |
| 87 | +```py |
| 88 | +class Solution: |
| 89 | + def longestOnes(self, A: List[int], K: int) -> int: |
| 90 | + i = ans = 0 |
| 91 | + |
| 92 | + for j in range(len(A)): |
| 93 | + K -= A[j] == 0 |
| 94 | + while K < 0: |
| 95 | + K += A[i] == 0 |
| 96 | + i += 1 |
| 97 | + ans = max(ans, j - i + 1) |
| 98 | + return ans |
| 99 | + |
| 100 | +``` |
| 101 | + |
| 102 | +甚至更简洁: |
| 103 | + |
| 104 | +```py |
| 105 | +class Solution: |
| 106 | + def longestOnes(self, A: List[int], K: int) -> int: |
| 107 | + i = 0 |
| 108 | + |
| 109 | + for j in range(len(A)): |
| 110 | + K -= 1 - A[j] |
| 111 | + if K < 0: |
| 112 | + K += 1 - A[i] |
| 113 | + i += 1 |
| 114 | + return j - i + 1 |
| 115 | +``` |
| 116 | + |
| 117 | +**复杂度分析** |
| 118 | + |
| 119 | +令 n 为数组长度。 |
| 120 | + |
| 121 | +- 时间复杂度:$O(n)$ |
| 122 | +- 空间复杂度:$O(1)$ |
| 123 | + |
| 124 | +> 此题解由 [力扣刷题插件](https://leetcode-pp.github.io/leetcode-cheat/?tab=solution-template) 自动生成。 |
| 125 | +
|
| 126 | +力扣的小伙伴可以[关注我](https://leetcode-cn.com/u/fe-lucifer/),这样就会第一时间收到我的动态啦~ |
| 127 | + |
| 128 | +以上就是本文的全部内容了。大家对此有何看法,欢迎给我留言,我有时间都会一一查看回答。更多算法套路可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 40K star 啦。大家也可以关注我的公众号《力扣加加》带你啃下算法这块硬骨头。 |
| 129 | + |
| 130 | +关注公众号力扣加加,努力用清晰直白的语言还原解题思路,并且有大量图解,手把手教你识别套路,高效刷题。 |
| 131 | + |
| 132 | + |
0 commit comments