常见面试算题题中的滑动窗口问题

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)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

love666666shen

谢谢您的鼓励!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值