1. 算法刷题实战:滑动窗口与模拟转圈
今天要分享的是两道非常经典的LeetCode题目——209题「长度最小的子数组」和59题「螺旋矩阵II」。这两道题分别代表了数组操作中两种重要的解题思路:滑动窗口技巧和模拟转圈法。作为刷过300+题的过来人,我觉得这两道题特别适合用来培养算法直觉。
2. 209. 长度最小的子数组
2.1 问题重述与暴力解法
给定一个含有n个正整数的数组和一个正整数target,找出该数组中满足其和≥target的长度最小的连续子数组,并返回其长度。如果不存在符合条件的子数组,返回0。
最直观的暴力解法是双重循环枚举所有可能的子数组:
def minSubArrayLen(target, nums): n = len(nums) min_len = float('inf') for i in range(n): current_sum = 0 for j in range(i, n): current_sum += nums[j] if current_sum >= target: min_len = min(min_len, j - i + 1) break return min_len if min_len != float('inf') else 0这个解法时间复杂度是O(n²),在LeetCode上会超时。我们需要更聪明的办法。
2.2 滑动窗口的精髓
滑动窗口的核心思想是维护一个窗口,通过调整窗口的左右边界来寻找最优解。具体到这道题:
- 初始化左指针left=0,当前和current_sum=0,最小长度min_len=∞
- 右指针right从0开始遍历数组:
- 将nums[right]加入current_sum
- 当current_sum≥target时:
- 更新min_len
- 尝试左移left缩小窗口
- 最后返回min_len
优化后的代码:
def minSubArrayLen(target, nums): n = len(nums) min_len = float('inf') left = current_sum = 0 for right in range(n): current_sum += nums[right] while current_sum >= target: min_len = min(min_len, right - left + 1) current_sum -= nums[left] left += 1 return min_len if min_len != float('inf') else 0关键点:为什么用while而不是if?因为左边界可能需要多次移动才能满足current_sum<target的条件。
2.3 复杂度分析与边界情况
时间复杂度:O(n),每个元素最多被访问两次(右指针一次,左指针一次) 空间复杂度:O(1),只使用了常数个额外空间
需要注意的特殊情况:
- 空数组输入
- 数组中所有元素和仍小于target
- 数组中存在单个元素就≥target的情况
3. 59. 螺旋矩阵II
3.1 问题理解与模拟思路
给定正整数n,生成一个包含1到n²所有元素,且元素按顺时针螺旋排列的n×n正方形矩阵。
这道题的关键在于准确模拟填充过程。我们需要:
- 确定填充的层数(n为奇数时需要单独处理中心点)
- 按照"右→下→左→上"的顺序填充每一圈
- 维护当前填充的数字和边界条件
3.2 分层填充的实现细节
具体实现时可以定义四个边界:
- 左边界left=0
- 右边界right=n-1
- 上边界top=0
- 下边界bottom=n-1
然后按照以下步骤填充:
def generateMatrix(n): matrix = [[0]*n for _ in range(n)] num = 1 left, right, top, bottom = 0, n-1, 0, n-1 while left <= right and top <= bottom: # 从左到右填充上边 for i in range(left, right+1): matrix[top][i] = num num += 1 top += 1 # 从上到下填充右边 for i in range(top, bottom+1): matrix[i][right] = num num += 1 right -= 1 # 从右到左填充下边 for i in range(right, left-1, -1): matrix[bottom][i] = num num += 1 bottom -= 1 # 从下到上填充左边 for i in range(bottom, top-1, -1): matrix[i][left] = num num += 1 left += 1 return matrix3.3 调试技巧与常见错误
这类模拟题最容易出现的问题:
- 边界条件处理不当(特别是奇数n的中心点)
- 填充顺序错误(必须严格遵循右→下→左→上)
- 索引越界(尤其在反向填充时)
调试时可以:
- 打印每步后的矩阵状态
- 使用小规模n(如n=3)手动模拟验证
- 特别注意循环终止条件
4. 两道题的共通技巧
虽然这两道题解法不同,但都体现了算法题的一些通用技巧:
- 先考虑暴力解法,再优化
- 画图辅助理解(特别是螺旋矩阵)
- 使用小规模测试案例验证
- 注意边界条件和特殊输入
对于滑动窗口,关键是要理解:
- 何时移动右边界(扩展窗口)
- 何时移动左边界(收缩窗口)
- 如何更新最优解
对于模拟题,最重要的是:
- 明确填充顺序和规则
- 准确维护边界条件
- 处理奇数/偶数不同情况
5. 刷题进阶建议
同类题目推荐:
- 滑动窗口:3(无重复字符最长子串)、76(最小覆盖子串)、904(水果成篮)
- 螺旋矩阵:54(螺旋矩阵)、885(螺旋矩阵III)
时间控制:
- 每道题给自己设定时间限制(如30分钟)
- 超过时间就参考题解,但必须完全理解
反复练习:
- 隔几天重新做这两道题
- 尝试不同语言实现
总结模板:
- 滑动窗口有相对固定的代码结构
- 模拟题要总结自己的坐标处理方式
我在最初做这两道题时,螺旋矩阵花了整整两个小时才AC。后来发现其实只要坚持按照固定方向模拟,注意边界调整,这类题就会变得很有规律。而滑动窗口的难点在于理解为什么它能降低复杂度,这需要对双指针有深刻理解。