LeetCode 15中等对撞双指针
三数之和 图解题解
排序之后一个固定、两端夹,命中后两侧同时收并跳过重复——三数之和的核心就这两步。
排序后固定第一个数,剩下两个从两端往中间夹:三数超了就把右指针左移换小的,不够就把左指针右移换大的,等于零就记录下来——但命中之后 l 和 r 要同时向内走,并且跳过紧跟着的相同值,防止记录重复三元组。固定值本身也要跳重复,否则整组就重了。这两处去重是本题最容易漏的细节。
这道题到底在问什么
找出数组里所有不重复的三元组,使三个数之和为 0。
- nums
- [-2, -2, 0, 2, 3]
- 输出
- [[-2, 0, 2]]
最优解:一步一步想明白
- 3排序后固定 i,问题就变成在 i 右边找两个数,让它们的和等于 −nums[i]。l 和 r 往中间夹:和大了 r 左移,和小了 l 右移,正好命中就记下来。
- 4[-2, -2, 0, 2, 3]先从小到大排个序。有序了才能用对撞双指针。
- 5l=1, r=4 · 和太小固定 −2,要在右边凑出和为 2 的两个数。当前三数和 = −1 小于 0,想让和变大,就把 l 右移(换个更大的数)。
- 6l=2, r=4 · 和太大l 移到下标 2(值 0),三数和 = 1 大于 0 了。这次反过来,把 r 左移(换个更小的数)。
- 7l=2, r=3 · 命中r 移到下标 3(值 2),三数和 = −2 + 0 + 2 = 0,命中!记录 [−2, 0, 2]。
- 8l→3, r→2 交叉命中后 l 右移、r 左移 同时进行(并跳过重复值),继续找。此时 l、r 交叉,固定第一个 −2 这轮结束。
- 9nums[1]==nums[0] → 去重轮到固定下标 1,可它也是 −2,和上一个固定值一样。再做一遍只会得到重复的 [−2,0,2],所以直接跳过——这就是「去重」。
- 10l=3, r=4 · 和>0固定 0:最小的两数之和都已经大于 0,r 左移后很快交叉,没有新解。
- 11答案 [[-2,0,2]]所有固定点试完,得到唯一三元组 [−2, 0, 2]。这一趟把小了右移、大了左移、命中、去重四种情况都演示了一遍。
- 14排序后用左右指针把两数之和「夹」出来——盛水容器、两数之和 II 都是这套对撞手感。
- 19把这题练到能复述后,再去同类题里迁移:先复用状态定义,再调整边界条件。
⚠️ 容易写错的地方
✗ 错:不排序就上双指针
✓ 对:必须先 nums.sort()
对撞的前提是有序
✗ 错:忘了去重
✓ 对:固定点和移动后都跳过相同值
否则出现重复三元组
✗ 错:命中后只动一个指针
✓ 对:命中要 l+=1 且 r-=1
否则原地重复命中
完整代码(Python / C++ / Java)
Python
class Solution:
def threeSum(self, nums):
nums.sort()
ans = []
n = len(nums)
for i in range(n - 2):
if nums[i] > 0:
break
if i > 0 and nums[i] == nums[i - 1]:
continue
l, r = i + 1, n - 1
while l < r:
s = nums[i] + nums[l] + nums[r]
if s == 0:
ans.append([nums[i], nums[l], nums[r]])
l += 1
r -= 1
while l < r and nums[l] == nums[l - 1]:
l += 1
while l < r and nums[r] == nums[r + 1]:
r -= 1
elif s < 0:
l += 1
else:
r -= 1
return ansC++
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
sort(nums.begin(), nums.end());
vector<vector<int>> ans;
int n = nums.size();
for (int i = 0; i + 2 < n; i++) {
if (nums[i] > 0) break;
if (i > 0 && nums[i] == nums[i-1]) continue;
int l = i + 1, r = n - 1;
while (l < r) {
int s = nums[i] + nums[l] + nums[r];
if (s == 0) {
ans.push_back({nums[i], nums[l], nums[r]});
l++; r--;
while (l < r && nums[l] == nums[l-1]) l++;
while (l < r && nums[r] == nums[r+1]) r--;
} else if (s < 0) l++;
else r--;
}
}
return ans;
}
};Java
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
Arrays.sort(nums);
List<List<Integer>> ans = new ArrayList<>();
int n = nums.length;
for (int i = 0; i + 2 < n; i++) {
if (nums[i] > 0) break;
if (i > 0 && nums[i] == nums[i-1]) continue;
int l = i + 1, r = n - 1;
while (l < r) {
int s = nums[i] + nums[l] + nums[r];
if (s == 0) {
ans.add(Arrays.asList(nums[i], nums[l], nums[r]));
l++; r--;
while (l < r && nums[l] == nums[l-1]) l++;
while (l < r && nums[r] == nums[r+1]) r--;
} else if (s < 0) l++;
else r--;
}
}
return ans;
}
}套路模板 · 对撞双指针
# 有序数组里找一对,逼近某个 target
l, r = 0, len(a) - 1
while l < r:
s = a[l] + a[r]
if s == target:
record(); l += 1; r -= 1 # 命中双移
elif s < target:
l += 1 # 要更大
else:
r -= 1 # 要更小// 有序数组里找一对,逼近某个 target
int l = 0, r = (int)a.size() - 1;
while (l < r) {
int s = a[l] + a[r];
if (s == target) {
record(); l++; r--; // 命中双移
} else if (s < target) {
l++; // 要更大
} else {
r--; // 要更小
}
}// 有序数组里找一对,逼近某个 target
int l = 0, r = a.length - 1;
while (l < r) {
int s = a[l] + a[r];
if (s == target) {
record(); l++; r--; // 命中双移
} else if (s < target) {
l++; // 要更大
} else {
r--; // 要更小
}
}复杂度
时间复杂度
O(n²)
排序 + 双层 n×n
空间复杂度
O(1)
不算排序与答案
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 三数之和 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么是 O(n²) 不是 O(n³)?+
排序 O(n log n);外层固定一个数 O(n),内层双指针 O(n),合计 O(n²)。
重复怎么去重?+
三个位置都跳过相邻相同值:固定数去重、左指针去重、右指针去重。
能用哈希吗?+
能(固定一数 + 两数之和哈希),但去重更麻烦;排序双指针是更干净的标准解。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。