167. 两数之和 II

中等 含交互动画

学完这道你应该能: 说清它为什么用「对撞双指针」,而不是死记步骤; 合上页面,自己默写出核心代码; 用 30 秒把思路讲清楚。

AI 私教 · 就着本题动画讲
卡住了不用硬扛,按 8 步把这题真正走通。
从读题、试解、找法到手写代码,边问边打勾,适合第一次系统学算法的同学。

题目描述

一个升序数组里找两个数,使它们相加等于 target,返回它们的下标(从 1 开始)。

numbers = [2, 5, 7, 9, 11, 14]
target = 20
输出 = [4, 5] (9 + 11 = 20)

思路解析

直接套两数之和 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)

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 三个变量,不像哈希表要存整个数组

套路模板

记住骨架:首尾出发、命中即返、偏大缩右、偏小进左。三数之和、盛水容器、平方数之和都是它的变体。

Python
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 时是自己加自己,应停下)

以上文字与上方动画完全同一思路:先看动画建立直觉 → 读文字巩固 → 关掉页面自己默写一遍代码,能写出来才算真的掌握

下一题 →11. 盛最多水的容器 ← 返回题库