
本文详解 leetcode 209 题“最小覆盖子数组长度”的滑动窗口解法,重点剖析原始实现中因循环终止条件不当导致漏解(如错过末尾合法子数组 `[3,4]`)的根本原因,并给出修正后的健壮代码及关键逻辑说明。
在解决“最小长度子数组,其元素和 ≥ target”这类问题时,滑动窗口(双指针)是时间复杂度最优(O(n))的标准方法。核心思想是维护一个动态窗口 [i, j)(左闭右开),通过扩展右端点 j 增加和,收缩左端点 i 减少和,持续更新满足条件的最短长度。
然而,一个极易被忽视却致命的细节在于循环的终止条件。原始代码使用 while j = target),此时仍需继续收缩左边界 i,以尝试获得更短的有效子数组(如示例 [2,3,1,2,4,3] 中,j=6 后窗口为 [2,3,1,2,4,3],其和为 15 ≥ 7,但收缩 i 可得到 [4,3] 这一长度为 2 的解)。
因此,正确的循环条件必须保证:只要当前窗口和仍满足条件,就应继续收缩左边界,即使右边界已越界。修正方案是将终止条件扩展为:
while j < len(nums) or sumSoFar >= target:
该条件确保两种情况均能继续执行循环体:
- j
- sumSoFar >= target:即使 j 已越界,仍需收缩左边界以探索更优解。
以下是完整、健壮的实现:
from math import inf
class Solution:
def minSubArrayLen(self, target: int, nums: list[int]) -> int:
i = 0
j = 0
sum_so_far = 0
min_len = inf
# 关键:循环持续至窗口无收缩价值为止
while j < len(nums) or sum_so_far >= target:
if sum_so_far >= target:
# 当前窗口有效,更新最小长度(注意:j-i 即 [i, j) 长度)
min_len = min(min_len, j - i)
# 收缩左边界
sum_so_far -= nums[i]
i += 1
else:
# 扩展右边界(仅当 j 未越界时才可取 nums[j])
sum_so_far += nums[j]
j += 1
return min_len if min_len != inf else 0关键注意事项:
- 索引与长度对应:本实现采用左闭右开区间 [i, j),故子数组长度恒为 j - i,逻辑清晰且不易出错;
- 边界安全:else 分支中 nums[j] 的访问受 j
- 初始化与返回:min_len 初始化为 inf,最终用 inf 检查无解情况,语义明确;
- 单次遍历保证:i 和 j 均只增不减,整体时间复杂度严格 O(n)。
运行示例验证:
sol = Solution() print(sol.minSubArrayLen(7, [2,3,1,2,4,3])) # 输出: 2 print(sol.minSubArrayLen(4, [1,4,4])) # 输出: 1 print(sol.minSubArrayLen(11, [1,1,1,1,1,1,1,1])) # 输出: 0
总结而言,滑动窗口的终止条件不是技术细节,而是算法正确性的基石。它必须完整覆盖所有潜在有效状态空间——不仅包括“扩展中”,也必须包含“收缩中”。理解并正确表达这一逻辑,是写出鲁棒双指针代码的关键一步。










