双指针与滑动窗口 · 数据结构与算法
怎么只走一遍就解决列表问题?
课程目录为什么需要它
面试题里很大一部分是关于列表(数组)的:找和为目标值的一对数、反转、给有序列表去重。两个套路,双指针和滑动窗口,能把很多 O(n²) 的暴力解法变成 O(n)。
直觉理解
双指针:在有序列表的两端各放一个下标。和太小,左边的往右挪;和太大,右边的往左挪。每一步都排除一个候选,所以最多 n 步。
滑动窗口:要看每一段连续的 k 个数时,不要每次都重新把 k 个数加一遍。滑动它:加上进入窗口的数,减去离开的数。
定义
双指针
Two indexes that move toward each other (or in the same direction) so each element is visited once.两个下标相向(或同向)移动,让每个元素只被访问一次。
滑动窗口
A range [left, right) over the list that moves forward, updating a running total instead of recomputing it.列表上一个向前移动的区间 [left, right),每次更新累计值,而不是重新计算。
示例
找和为目标值的一对数
动手试试列表是有序的,所以可以从两端往中间夹。
def pair_sum(xs, target):
left, right = 0, len(xs) - 1
while left < right:
total = xs[left] + xs[right]
if total == target:
return (xs[left], xs[right])
if total < target:
left += 1
else:
right -= 1
return None
print(pair_sum([1, 3, 4, 6, 9, 11], 13))逐步执行
- L2从两端开始:1 和 11。
- L71 + 11 = 12 太小:左边右移。
- L103 + 11 = 14 太大:右边左移。
- L133 + 9 = 13。
(3, 9)
最好的连续 3 天
动手试试每天的销量;找出最好的连续 3 天。窗口每次滑动一天。
sales = [4, 2, 7, 1, 8, 3, 5]
k = 3
window = sum(sales[:k])
best = window
for i in range(k, len(sales)):
window += sales[i] - sales[i - k]
best = max(best, window)
print(best)逐步执行
- L3第一个窗口:4 + 2 + 7 = 13。
- L6加上进来的那天,减去离开的那天。
- L8各个窗口:13、10、16、12、16。最好是 16。
16
常见错误
在无序列表上用双指针
往中间夹之所以有效,是因为列表有序。无序的话,先排序(O(n log n)),或者用集合(下一课)。
差一错误
range(k, len(xs)) 和 xs[i - k] 必须对得上。手动走一遍前两步。
小测验
1. 这段代码会输出什么?
xs = [1, 2, 3, 4, 5]
left, right = 0, len(xs) - 1
while left < right:
xs[left], xs[right] = xs[right], xs[left]
left += 1
right -= 1
print(xs)查看答案
答案:A
交换两端再往里走:这是原地反转列表。
2. 在 n 个数上滑动长度为 k 的窗口,用累计值的方法大约需要多少次加法?
查看答案
答案:A
第一个窗口 k 次,之后每步一加一减:O(n)。
动手练习
3 道练习,分三层:先预测程序的输出,再按新需求改程序,最后从零写一个。代码在浏览器里运行,提交马上判题。
- 预测输出跟踪指针
准确写出这段程序的输出。
- 改需求一遍去重
dedupe(xs)收到一个有序列表,返回去掉重复后的列表。里面那层循环对每个元素都把out扫一遍(O(n²))。既然列表有序,重复的元素只可能等于最后一个保留下来的元素。利用这一点把它改成 O(n)。 - 从零写最好的窗口
写函数
best_window(xs, k),返回 xs 中任意连续 k 个数之和的最大值(假设 len(xs) ≥ k ≥ 1)。用滑动窗口:O(n)。
本课术语
| 术语 | 中文 | 英文定义 |
|---|---|---|
| Array | 数组 | Items stored in order and reached by index; Python's list. |
| Two pointers | 双指针 | Two indexes moving through a list so each item is visited once. |
| Sliding window | 滑动窗口 | A moving range with a running total. |