题目描述
一个升序数组里找两个数,使它们相加等于 target,返回它们的下标(从 1 开始)。
思路解析
直接套两数之和 I 的哈希表也对,O(n) 时间、但要 O(n) 额外空间存「见过的数」。题目特意给了升序这个条件——不利用就亏了。
因为有序,l 指头(最小)、r 指尾(最大)。和太大就把 r 左移换个更小的,和太小就把 l 右移换个更大的。有序保证了「往哪移、和往哪变」是确定的——这就是从哈希到双指针的关键转折,省掉了那张哈希表。
准备 · 首尾出发:左指针 l=0 指向最小的 2,右指针 r=5 指向最大的 14。从这一对最「极端」的组合开始试。
第 1 次 · 算和:当前两头之和 s = 2 + 14 = 16。拿它和目标 20 比大小,再决定动哪个指针。
第 1 次 · 偏小 → l 右移:16 比 20 小。r 已经是最大的 14 了,没法再大,只能让 l 右移,换一个更大的左数把和抬上去。
第 2 次 · 还是偏小:l 来到 5:s = 5 + 14 = 19,还差 1,仍然偏小。l 继续右移。左边的 2 已被排除(灰)。
第 3 次 · 算和:l 来到 7:s = 7 + 14 = 21,第一次超过了 20。这回该动哪边?
第 3 次 · 偏大 → r 左移:21 偏大。l 指的 7 已经是当前最小可用左数,只能让 r 左移,丢掉最大的 14,换个更小的右数把和压下来。
第 4 次 · 又偏小:r 退到 11:s = 7 + 11 = 18,又偏小了。14 已被排除(灰)。l 右移换更大的左数。
第 5 次 · 命中!:l 来到 9:s = 9 + 11 = 20,正好等于 target!返回它们的下标(从 1 数):[4, 5]。全程指针只走了一遍,没回过头。
当 s > target 时,l 配「当前最大的 r」都嫌大,那 l 配比 r 更小的任何数只会更小、更不可能=target——所以 l 和 r 这一对、以及 l 和 r 左边所有数的组合都可安全排除,r 左移不漏解。和太小时同理可证 l 右移安全。
只要数组有序、要找「一对满足某和/差」,就首尾双指针:一次比较砍掉一头,O(n) 搞定。
参考代码(Python)
1l, r = 0, len(numbers) - 1
2while l < r:
3 s = numbers[l] + numbers[r] # 两头之和
4 if s == target: return [l + 1, r + 1] # 命中,下标从1开始
5 elif s > target: r -= 1 # 太大,缩右
6 else: l += 1 # 太小,进左复杂度分析
- 时间复杂度:O(n) —— l、r 一头一尾相向而行,加起来只走一遍 n
- 空间复杂度:O(1) —— 只用 l、r、s 三个变量,不像哈希表要存整个数组
套路模板
记住骨架:首尾出发、命中即返、偏大缩右、偏小进左。三数之和、盛水容器、平方数之和都是它的变体。
1# 有序数组找一对满足条件的元素都套
2l, r = 0, n - 1
3while l < r:
4 if 满足(l, r): return ... # 命中
5 elif 偏大: r -= 1 # 缩右变小
6 else: l += 1 # 进左变大易错点
- 错误写法:在无序数组上直接用双指针 → 正确写法:先确认(或先排序成)升序(「偏大就缩右」的正确性完全依赖单调性,无序时缩掉的那半里可能正藏着答案)
- 错误写法:return [l, r] → 正确写法:return [l + 1, r + 1](本题要求下标从 1 开始,忘了 +1 整组答案都会偏移一位)
- 错误写法:while l <= r → 正确写法:while l < r(同一个数不能用两次,l==r 时是自己加自己,应停下)
以上文字与上方动画完全同一思路:先看动画建立直觉 → 读文字巩固 → 关掉页面自己默写一遍代码,能写出来才算真的掌握。