【滑动窗口算法实战】最小子串和最小覆盖子串问题(JS + Python 双解)

3 阅读3分钟

【滑动窗口算法实战】最小子串和最小覆盖子串问题(JS + Python 双解)

🧠 引言

你是否遇到过这些场景?

  • 在字符串中找出“最短连续子串”,满足某些条件
  • 判断一个数组内是否存在“某长度内的目标和”
  • 滑动窗口模板写了又忘,记不住!

别怕,本篇将通过两个经典问题:

  1. 最小和子数组 ≥ target
  2. 最小覆盖子串(LeetCode 困难题)

来系统讲解滑动窗口的思维模型与实战技巧,并用 JS + Python 双解写出高性能代码!


🧱 一、什么是滑动窗口?

滑动窗口是一种通过维护区间范围(如 [left, right])动态调整子串或子数组范围的技巧,常用于查找最优区间问题。


🧪 Part 1:最短子数组长度(中等)

❓题目描述(LeetCode 209)

给定正整数数组 nums 和目标值 target,找出和 ≥ target 的最短子数组长度,返回其长度。如果不存在则返回 0。

示例:

输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:[4,3] 是最短的子数组

✅ 解法思路(滑动窗口)

  1. 用两个指针 startend 表示窗口
  2. 向右扩展 end,累加窗口和
  3. 当窗口和 ≥ target,尝试缩小 start 来更新最短长度

💻 JavaScript 实现

function minSubArrayLen(target, nums) {
  let left = 0, sum = 0, minLen = Infinity;

  for (let right = 0; right < nums.length; right++) {
    sum += nums[right];

    while (sum >= target) {
      minLen = Math.min(minLen, right - left + 1);
      sum -= nums[left++];
    }
  }

  return minLen === Infinity ? 0 : minLen;
}

🐍 Python 实现

def min_sub_array_len(target, nums):
    left, total = 0, 0
    min_len = float('inf')

    for right in range(len(nums)):
        total += nums[right]
        while total >= target:
            min_len = min(min_len, right - left + 1)
            total -= nums[left]
            left += 1

    return 0 if min_len == float('inf') else min_len

🧪 Part 2:最小覆盖子串(困难)

❓题目描述(LeetCode 76)

给定字符串 st,返回 s 中包含 t 所有字符的最小子串。若无解则返回空串。

输入: s = "ADOBECODEBANC", t = "ABC"
输出: "BANC"

✅ 解法思路(滑动窗口 + 计数器)

  1. 用两个指针维护窗口
  2. 用字典统计 t 中需要的字符频率
  3. 移动 right 扩大窗口,直到包含所有字符
  4. 再移动 left 尝试缩小窗口

💻 JavaScript 实现

function minWindow(s, t) {
  const need = {};
  for (const c of t) need[c] = (need[c] || 0) + 1;

  let left = 0, count = 0;
  let minLen = Infinity, res = '';
  const window = {};

  for (let right = 0; right < s.length; right++) {
    const c = s[right];
    window[c] = (window[c] || 0) + 1;

    if (need[c] && window[c] === need[c]) count++;

    while (count === Object.keys(need).length) {
      if (right - left + 1 < minLen) {
        minLen = right - left + 1;
        res = s.slice(left, right + 1);
      }

      const d = s[left++];
      if (need[d]) {
        if (window[d] === need[d]) count--;
        window[d]--;
      }
    }
  }

  return res;
}

🐍 Python 实现

from collections import Counter

def min_window(s, t):
    need = Counter(t)
    window = {}
    left = 0
    valid = 0
    start, length = 0, float('inf')

    for right, c in enumerate(s):
        window[c] = window.get(c, 0) + 1
        if c in need and window[c] == need[c]:
            valid += 1

        while valid == len(need):
            if right - left + 1 < length:
                start, length = left, right - left + 1

            d = s[left]
            left += 1
            if d in need:
                if window[d] == need[d]:
                    valid -= 1
                window[d] -= 1

    return "" if length == float('inf') else s[start:start + length]

⚠️ 易错点分析

场景正确做法
窗口字符多但不满足需要只在匹配字符频次后计数有效
不判断 minLen === Infinity输出时需返回空串或 0
漏判断窗口收缩时更新 valid及时递减匹配字符计数

🧩 拓展任务

  • 改写为找“最长”符合条件的子串
  • 支持“重复字符 + 区分大小写”情形
  • 用 Trie 替代哈希结构处理大规模字符集

📚 总结一句话

滑动窗口是一种处理区间类问题的通用模型,掌握它,就能轻松搞定数组和字符串中的“最小/最大/最短”问题!


📘 下一篇预告:

第12篇:【用双指针解决有序数组问题】JS 与 Python 解“两数之和 II”和“移动零”!