Python算法训练序列
双指针与滑动窗口 · Unit 07
窗口第一次满足条件,
只是收缩的开始。
寻找最短可行区间时,right负责把窗口和扩张到目标以上;一旦满足条件,就不断移动left,逐步剥离左端元素,并在每次仍然可行时更新更短答案。
题目要求
给定一个由正整数组成的数组 nums 和一个正整数 target,找出总和大于等于 target 的最短连续子数组长度。如果不存在这样的子数组,返回 0。
约束
1 ≤ target ≤ 10⁹1 ≤ len(nums) ≤ 1000001 ≤ nums[i] ≤ 10000
示例
输出:2
最短子数组为[4,3]
输出:1
输出:0
关键前提
数组元素全部为正数,因此right右移时窗口和不会减小,left右移时窗口和不会增大。这一单调性是滑动窗口成立的基础。
窗口长度由 len(p) 决定;每轮更新进入和离开的字符频次,判断组成是否匹配。
窗口长度由 current_sum 是否达到 target 决定;right 负责达到条件,left 负责继续压缩长度。
当前循环不变量
current_sum始终等于nums[left:right+1]的元素和。
当前移动规则
和不足时继续扩张;和达到target后,更新best并持续收缩,直到窗口重新不满足。
朴素方法从每个left重新向右求和,最坏需要O(n²)。由于元素全为正,旧窗口去掉左端后,其余元素和仍可直接复用,无需重新累计。
迁移题:LeetCode #713 乘积小于 K 的子数组
先完整认识新题,再判断 #209 的窗口骨架还能复用多少。读题检查完成前,不进入批量计数公式和代码迁移。
题目要求
给定整数数组 nums 和整数 k,返回所有元素乘积严格小于 k 的非空连续子数组数量。
约束
1 ≤ len(nums) ≤ 3 × 10⁴1 ≤ nums[i] ≤ 10000 ≤ k ≤ 10⁶
官方示例
输出:8
合法子数组:[10]、[5]、[2]、[6]、[10,5]、[5,2]、[2,6]、[5,2,6]
输出:0
严格不等号
[10,5,2] 的乘积等于100,因此不合法;必须满足乘积 < k,而不是 ≤ k。
读题检查 1|函数最终返回什么?
读题检查 2|为什么 [10,5,2] 不计入,而 [5,2,6] 可以计入?
已经掌握:#209 长度最小的子数组
准备迁移:#713 乘积小于 K 的子数组
[left,right] 的乘积小于 k。固定右端 right,起点取 left、left+1、…、right 时,只会继续移除至少为1的正因子,乘积不会变大,因此这 right-left+1 个后缀子数组全部合法。
下面哪句话最准确地描述了迁移幅度?
槽位 A|为什么要先处理 k ≤ 1?
槽位 B|right 加入新元素时怎样更新窗口状态?
槽位 C|乘积不合法时怎样持续收缩?
槽位 D|窗口恢复合法后怎样统计并返回?
left=0,right 逐项向右;窗口不满足要求时使用 while 推进 left。current_sum += / -= 替换为 product *= / //=。right-left+1。#209:求最短可行长度
left = 0 current_sum = 0 best = n + 1 for right in range(n): current_sum += nums[right] while current_sum >= target: best = min(best, right-left+1) current_sum -= nums[left] left += 1 return best / 0
#713:统计全部合法子数组
if k <= 1: return 0 left = 0 product = 1 answer = 0 for right in range(n): product *= nums[right] while product >= k: product //= nums[left] left += 1 answer += right-left+1 return answer
可选挑战:脱离槽位,独立重写完整代码
这一步不阻碍主流程。先完整运行官方样例,再凭“乘入—超限时除去—合法后批量计数”重写。
本单元形成的算法Skill
面对正数数组中的最短满足型连续区间,right负责扩大状态直到条件成立;条件成立后,必须用while持续移动left,并在每次收缩前更新答案。正数保证窗口和具有单调性,使已经移出的左端无需重新考虑。