【滑动窗口算法实战】最小子串和最小覆盖子串问题(JS + Python 双解)
🧠 引言
你是否遇到过这些场景?
- 在字符串中找出“最短连续子串”,满足某些条件
- 判断一个数组内是否存在“某长度内的目标和”
- 滑动窗口模板写了又忘,记不住!
别怕,本篇将通过两个经典问题:
- 最小和子数组 ≥ target
- 最小覆盖子串(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] 是最短的子数组
✅ 解法思路(滑动窗口)
- 用两个指针
start
和end
表示窗口 - 向右扩展
end
,累加窗口和 - 当窗口和 ≥ 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)
给定字符串 s
和 t
,返回 s
中包含 t
所有字符的最小子串。若无解则返回空串。
输入: s = "ADOBECODEBANC", t = "ABC"
输出: "BANC"
✅ 解法思路(滑动窗口 + 计数器)
- 用两个指针维护窗口
- 用字典统计
t
中需要的字符频率 - 移动
right
扩大窗口,直到包含所有字符 - 再移动
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”和“移动零”!