滑动窗口求最小连续子数组长度:终止条件的正确设计

2次阅读

滑动窗口求最小连续子数组长度:终止条件的正确设计

本文详解 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

总结而言,滑动窗口的终止条件不是技术细节,而是算法正确性的基石。它必须完整覆盖所有潜在有效状态空间——不仅包括“扩展中”,也必须包含“收缩中”。理解并正确表达这一逻辑,是写出鲁棒双指针代码的关键一步。

text=ZqhQzanResources