Python滑动窗口算法实战:3道LeetCode高频题保姆级解析
在算法面试和技术笔试中,滑动窗口(Sliding Window)是一个高频出现的核心技巧。很多开发者初次接触时,往往觉得它有些“玄学”——代码不长,但边界条件和指针移动的逻辑却容易让人绕晕。实际上,一旦你掌握了它的核心思想,就会发现它是一把解决特定类型问题的利器,尤其擅长处理连续子数组或子串的相关问题。今天,我们就抛开那些教科书式的定义,直接从三道经典的LeetCode高频题入手,手把手带你拆解思路,分析每一个易错点,让你不仅会写代码,更能理解背后的“为什么”。
本文面向的是有一定Python基础,正在准备技术面试或希望系统提升算法能力的开发者。我们将从最基础的“长度最小的子数组”开始,逐步深入到“乘积小于K的子数组”和“无重复字符的最长子串”。每一道题,我们都会先剖析问题本质,然后像搭积木一样构建出滑动窗口的解法,最后还会讨论一些常见的“坑”和优化思路。准备好了吗?让我们开始这场从理解到精通的旅程。
1. 滑动窗口的核心思想与适用场景
在深入具体题目之前,我们必须先建立对滑动窗口的直觉理解。你可以把它想象成用一个可伸缩的框,在一个序列(数组或字符串)上从左向右滑动。这个框的左右边界通常由两个指针(left 和 right)来界定。窗口内的元素,就是我们当前正在考察的连续子序列。
滑动窗口算法之所以高效,核心在于它利用了问题的单调性。对于许多问题,当右指针向右移动(扩大窗口)时,窗口的某个属性(如元素和、乘积)是单调变化的(通常是增加)。而当这个属性不满足题目条件时,我们就通过移动左指针(缩小窗口)来调整,寻找下一个可能的解。这个过程避免了暴力枚举所有子序列的 O(n²) 复杂度,通常能将时间复杂度优化到 O(n)。
那么,什么样的问题适合用滑动窗口呢?我总结了几个关键特征:
- 问题目标针对连续子序列:无论是子数组还是子串,必须是连续的。
- 需要寻找满足某个条件的最值:例如“最短长度”、“最大长度”、“子数组个数”等。
- 窗口的扩张与收缩有明确规则:当右指针加入新元素后,能快速判断当前窗口是否“合法”。如果不合法,左指针的移动能使窗口重新变得“合法”。
一个非常重要的前提是,序列中的元素通常需要是非负整数。为什么?因为对于求和或求积的问题,只有元素非负,右指针右移(加入新元素)才会导致窗口总和或乘积单调不减,左指针右移(移除元素)才会导致其单调不增。这种单调性是双指针正确移动、避免错过解的基础。如果存在负数,窗口和的变化就不再单调,简单的双指针滑动就可能失效,需要考虑更复杂的解法,如前缀和加哈希表。
下面这个表格对比了暴力枚举与滑动窗口的差异,能让你更直观地感受其优势:
| 对比维度 | 暴力枚举 (Nested Loops) | 滑动窗口 (Two Pointers) |
|---|---|---|
| 时间复杂度 | O(n²) 或更高 | 通常为 O(n) |
| 空间复杂度 | O(1) | O(1) 或 O(字符集大小) |
| 核心操作 | 固定左端点,枚举所有右端点 | 左右指针协同,单向扫描 |
| 适用问题 | 通用,但效率低 | 连续子序列的极值或计数问题 |
| 代码逻辑 | 直观,但冗长 | 简洁,但对边界条件要求高 |
理解了这些基础,我们就可以带着“寻找连续子序列、利用单调性、双指针协同扫描”的思路,进入实战环节了。
2. 实战一:长度最小的子数组 (LeetCode 209)
题目简述:给定一个含有 n 个正整数的数组和一个正整数 target,找出该数组中满足其和 ≥


2671

被折叠的 条评论
为什么被折叠?



