一句话总结
滑动窗口:维护一个 [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)。