滑动窗口与双指针算法
滑动窗口和双指针是解决数组/字符串问题的常用技巧,它们通过维护一个窗口或两个指针来高效地解决问题,避免暴力解法的时间复杂度。
滑动窗口(Sliding Window)
基本概念
滑动窗口是一种在数组或字符串上维护一个窗口的技术,通过移动窗口的左右边界来解决问题。滑动窗口主要分为两种类型:
- 固定窗口:窗口大小固定不变
- 可变窗口:窗口大小根据条件动态变化
核心思想
- 使用两个指针(left 和 right)表示窗口的左右边界
- 通过移动右指针扩展窗口,移动左指针收缩窗口
- 在窗口移动过程中维护窗口的状态(如和、最大值、字符计数等)
固定窗口
窗口大小固定为 k,每次移动一个位置。
算法模板
1 | func fixedWindow(nums []int, k int) { |
示例:滑动窗口最大值
1 | package main |
可变窗口
窗口大小根据条件动态变化,通常用于找满足条件的子数组/子字符串。
算法模板
1 | func variableWindow(nums []int) { |
示例:无重复字符的最长子串
1 | // LeetCode 3. 无重复字符的最长子串 |
双指针(Two Pointers)
双指针技术使用两个指针在数组或字符串中协同工作,通常用于优化暴力解法。
快慢指针
快慢指针是双指针的一种,两个指针以不同的速度移动。
应用场景
- 检测链表中的环:Floyd 判圈算法
- 找链表的中间节点
- 删除链表的倒数第 N 个节点
- 数组去重
示例代码
1 | package main |
左右指针
左右指针从数组的两端向中间移动,通常用于有序数组或字符串。
应用场景
- 二分查找
- 两数之和(有序数组)
- 反转数组/字符串
- 回文串判断
- 盛最多水的容器
示例代码
1 | package main |
算法对比与选择
滑动窗口 vs 双指针
| 特性 | 滑动窗口 | 双指针 |
|---|---|---|
| 适用场景 | 子数组/子字符串问题 | 有序数组、链表问题 |
| 窗口性质 | 通常维护连续区间 | 可以是任意两个位置 |
| 移动方式 | 左右边界协同移动 | 根据条件独立移动 |
| 典型问题 | 最长无重复子串、最小覆盖子串 | 两数之和、反转数组 |
选择建议
使用滑动窗口:
- 需要找满足条件的连续子数组/子字符串
- 需要维护窗口内的状态(如字符计数、和等)
- 问题涉及”最长”、”最短”、”包含”等关键词
使用快慢指针:
- 链表相关问题(环检测、找中点)
- 数组去重、移动零等问题
使用左右指针:
- 有序数组的查找问题
- 需要从两端向中间移动的问题
- 回文串、反转等问题
时间复杂度分析
- 滑动窗口:O(n),每个元素最多被访问两次(左指针和右指针各一次)
- 双指针:O(n),两个指针总共遍历数组一次
总结
滑动窗口和双指针是解决数组/字符串问题的高效技巧:
- 滑动窗口适合处理连续子区间问题,通过维护窗口状态来优化
- 快慢指针适合处理链表和数组的特定位置问题
- 左右指针适合处理有序数组的查找和比较问题
掌握这些技巧可以显著提高解题效率,避免暴力解法的 O(n²) 或更高时间复杂度。
参考文献
- LeetCode 滑动窗口专题
- LeetCode 双指针专题
- 《算法导论》
- 《数据结构与算法分析》