滑动窗口与双指针怎么用?

2025年 阅读约 15 分钟 面试指南 · 算法

深入解析滑动窗口与双指针:最长无重复子串、最小覆盖子串、三数之和、接雨水、盛水最多的容器,附面试模拟问答。

一句话总结

滑动窗口:维护一个 [left, right) 窗口,right 右移扩大窗口,left 右移缩小窗口,常用于子串/子数组问题。模板:while right < n: 加入 nums[right]; while 窗口不满足条件: 移除 nums[left], left++双指针:左右指针相向移动(如两数之和、接雨水)或快慢指针(如环检测)。核心是单调性——指针移动时条件单调变化。

初级理解

滑动窗口模板

# 滑动窗口通用模板 def sliding_window(s): left = 0 window = {} # 窗口内数据 result = 0 for right in range(len(s)): # 1. 扩大窗口:加入 s[right] window[s[right]] = window.get(s[right], 0) + 1 # 2. 缩小窗口:当窗口不满足条件时 while 窗口需要收缩: window[s[left]] -= 1 if window[s[left]] == 0: del window[s[left]] left += 1 # 3. 更新结果 result = max(result, right - left + 1) return result

最长无重复子串(LeetCode 3)

def length_of_longest_substring(s): left = 0 window = set() max_len = 0 for right in range(len(s)): # 缩小窗口直到没有重复 while s[right] in window: window.remove(s[left]) left += 1 window.add(s[right]) max_len = max(max_len, right - left + 1) return max_len
一句话总结:滑动窗口 = right 扩大 + left 缩小 + 更新结果。

中级深入

最小覆盖子串(LeetCode 76)

def min_window(s, t): need = {} for c in t: need[c] = need.get(c, 0) + 1 window = {} left = 0 valid = 0 # 已满足的字符数 start, min_len = 0, float('inf') for right in range(len(s)): c = s[right] if c in need: window[c] = window.get(c, 0) + 1 if window[c] == need[c]: valid += 1 # 所有字符都满足 → 尝试缩小窗口 while valid == len(need): if right - left + 1 < min_len: start = left min_len = right - left + 1 d = s[left] left += 1 if d in need: if window[d] == need[d]: valid -= 1 window[d] -= 1 return s[start:start+min_len] if min_len != float('inf') else ""

三数之和(LeetCode 15)— 排序 + 双指针

def three_sum(nums): nums.sort() result = [] for i in range(len(nums) - 2): if nums[i] > 0: break # 剪枝 if i > 0 and nums[i] == nums[i-1]: continue # 去重 left, right = i + 1, len(nums) - 1 while left < right: total = nums[i] + nums[left] + nums[right] if total == 0: result.append([nums[i], nums[left], nums[right]]) # 去重 while left < right and nums[left] == nums[left+1]: left += 1 while left < right and nums[right] == nums[right-1]: right -= 1 left += 1 right -= 1 elif total < 0: left += 1 else: right -= 1 return result
中级要点:最小覆盖子串用 valid 计数判断窗口是否满足;三数之和排序后固定一个 + 双指针。

高级拓展

接雨水(LeetCode 42)— 双指针

def trap(height): if not height: return 0 left, right = 0, len(height) - 1 left_max = right_max = 0 water = 0 while left < right: left_max = max(left_max, height[left]) right_max = max(right_max, height[right]) # 哪边低处理哪边 if left_max < right_max: water += left_max - height[left] left += 1 else: water += right_max - height[right] right -= 1 return water # 原理:每个位置的雨水量 = min(左边最高, 右边最高) - 当前高度 # 双指针优化:哪边低处理哪边,因为低的那边已经确定了边界

盛水最多的容器(LeetCode 11)

def max_area(height): left, right = 0, len(height) - 1 max_water = 0 while left < right: h = min(height[left], height[right]) w = right - left max_water = max(max_water, h * w) # 移动较矮的一边(移动高的不会增加面积) if height[left] < height[right]: left += 1 else: right -= 1 return max_water

实战场景

场景:找到字符串中所有字母异位词(LeetCode 438)

def find_anagrams(s, p): need = {} for c in p: need[c] = need.get(c, 0) + 1 window = {} left = 0 valid = 0 result = [] for right in range(len(s)): c = s[right] if c in need: window[c] = window.get(c, 0) + 1 if window[c] == need[c]: valid += 1 # 窗口大小等于 p 的长度时判断 if right - left + 1 == len(p): if valid == len(need): result.append(left) d = s[left] left += 1 if d in need: if window[d] == need[d]: valid -= 1 window[d] -= 1 return result

面试模拟

面试官:滑动窗口的通用模板是什么?

你:right 指针遍历数组/字符串,每次将 right 元素加入窗口;当窗口不满足条件时,while 循环移动 left 缩小窗口;每次更新结果。关键是明确窗口的"满足条件"是什么,以及何时更新结果(扩大窗口后还是缩小窗口后)。

面试官:接雨水怎么用双指针?

你:左右指针从两端向中间移动,维护 left_max 和 right_max。哪边的 max 小就处理哪边:当前雨水量 = 该侧 max - 当前高度。因为较小的一侧已经确定了边界(另一侧至少有这么高)。时间复杂度 O(n),空间 O(1)。