LeetCode1004. 最大连续1的个数 III
题目描述
给定一个由若干 0 和 1 组成的数组 A,最多可以将数组A中的 K 个元素的值从 0 变成 1 ,返回仅包含 1 的最长(连续)子数组的长度。
示例 1:
输入:A = [1,1,1,0,0,0,1,1,1,1,0], K = 2
输出:6
解释:
[1,1,1,0,0,1,1,1,1,1,1]
粗体数字从 0 翻转到 1,最长的子数组长度为 6。
示例 2:
输入:A = [0,0,1,1,0,0,1,1,1,0,1,1,0,0,0,1,1,1,1], K = 3
输出:10
解释:
[0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1,1]
粗体数字从 0 翻转到 1,最长的子数组长度为 10。
提示:
1 <= A.length <= 20000
0 <= K <= A.length
A[i] 为 0 或 1
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/max-consecutive-ones-iii
解题思路
题目翻译:
题意转换:把「最多可以把 K 个 0 变成 1,求仅包含 1 的最长子数组的长度」转换为 「找出一个最长的子数组,该子数组内最多允许有 K 个 0 」。
经过上面的题意转换,我们可知本题是求最大连续子区间,可以使用滑动窗口方法。滑动窗口的限制条件是:窗口内最多有 K 个 0。
代码思路:
1. 使用 left 和 right 两个指针,分别指向滑动窗口的左右边界;
2. right 主动右移:right 指针每次移动一步。当 A[right] 为 0,说明滑动窗口内增加了一个 0;
3. left 被动右移:判断此时窗口内 0 的个数,如果超过了 K,则 left 指针被迫右移,直至窗口内0 的个数小于等于 K为止;
4. 滑动窗口长度的最大值就是所求。
代码实现
下面是golang代码实现:
func longestOnes(A []int, K int) int {
n := len(A)
left, right := 0, 0 // 左右指针,初始时都指向数组首元素
res := 0 // 当前包含0和1的最长子数组长度
zeros := 0 // 统计当前子数组中0的个数
for right < n { // 右指针
if A[right] == 0 { // right指针指向0,则0的个数加1
zeros++
}
for zeros > K { // 当0的个数超过K时,可能左边边界为0,也可能右边界新增0
if A[left] == 0 { // 当左边界指向0时,0的个数减1
zeros--
}
left++ // PS:不管是左边界,还是右边界遇到0,且0的个数超过K,left都需要后移
}
res = max(res, right - left + 1) // 当前最长子数组数目,取res和right - left + 1中最大值
right++ // 右边界后移
}
return res
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
运行效果:
复杂度分析
由于需要对数组遍历一次,所以时间复杂度为O(n);
借助的中间遍历为常数个,空间复杂度为O(1)。
滑动窗口类型解题模板
《挑战程序设计竞赛》这本书中把滑动窗口叫做「虫取法」,我觉得非常生动形象。因为滑动窗口的两个指针移动的过程和虫子爬动的过程非常像:前脚不动,把后脚移动过来;后脚不动,把前脚向前移动。
下面分享一个滑动窗口的模板,能解决大多数的滑动窗口问题:
func findSubArray(nums []int):
N := len(nums) // 数组或字符串长度
left, right := 0, 0 // 双指针,表示当前遍历的区间[left, right],闭区间
sums := 0 // 用于统计 子数组或子区间 是否有效,根据题目可能会改成求和/计数
res := 0 // 记录最大的满足题目要求的 子数组或子串 长度
for right < N { // 当右边的指针没有搜索到 数组或字符串 的结尾
sums += nums[right] // 增加当前右边指针的数字/字符的求和/计数
for 区间[left, right]不符合题意{ // 此时需要一直移动左指针,直至找到一个符合题意的区间
sums -= nums[left] // 移动左指针前需要从counter中减少left位置字符的求和/计数
left += 1 // 真正的移动左指针,注意不能跟上面一行代码写反
// 到 for 结束时,我们找到了一个符合题意要求的 子数组/子串
res = max(res, right - left + 1) // 需要更新子数组长度的结果
right += 1 // 移动右指针,去探索新的区间
}
return res
滑动窗口中用到了左右两个指针,它们移动的思路是:以右指针作为驱动,拖着左指针向前走。右指针每次只移动一步,而左指针在内部 for 循环中每次可能移动多步。右指针是主动前移,探索未知的新区域;左指针是被迫移动,负责寻找满足题意的区间。
模板的整体思想是:
1. 定义两个指针 left 和 right 分别指向区间的开头和结尾,注意是闭区间;定义 sums 用来统计该区间内的特定字符出现的次数;
2. 第一重 for 循环是为了判断 right 指针的位置是否超出了数组边界;当 right 每次到了新位置,需要加上 right 指针指向元素 的和/计数;
3. 第二重 for 循环是让 left 指针向右移动到 [left, right] 区间符合题意的位置;当 left 每次移动到了新位置,需要减去 left 指针的 指向元素/计数;
4. 在第二重 for 循环之后,成功找到了一个符合题意的 [left, right] 区间,题目要求最大的区间长度,因此更新 res 为 max(res, 当前区间的长度) 。
5. right 指针每次向右移动一步,开始探索新的区间。
模板中的 sums 需要根据题目意思具体去修改,本题是求和题目因此把sums 定义成整数用于求和;如果是计数题目,就需要改成map用于计数。当左右指针发生变化的时候,都需要更新 sums 。
另外一个需要根据题目去修改的是内层 for 循环的判断条件,即: 区间 [left, right]不符合题意的情况 。对于本题而言,就是该区间内的 0 的个数 超过了 K 。
参考链接:https://leetcode-cn.com/problems/max-consecutive-ones-iii/solution/fen-xiang-hua-dong-chuang-kou-mo-ban-mia-f76z/
来源:力扣(LeetCode)