#15
中等
高频
双指针三数之和
这是一道围绕数组、双指针、排序展开的高频练习。建议先掌握「双指针」这套写法,再结合下方步骤讲解理解状态维护、边界处理和复杂度取舍。
数组
双指针
排序
题目分析
给你一个整数数组,要求找出所有和为 0 的三元组。
每个三元组里的三个位置必须不同,而且结果里不能有重复组合。
比如在 [-1, 0, 1, 2, -1, -4] 中,符合条件的答案有 [-1, -1, 2] 和 [-1, 0, 1]。
一句话概括:
从数组里找出所有不重复的三元组,让它们的和等于 0。
接下来怎么看推荐代码: 带着这个理解再看推荐代码时,重点观察这条主线:先排序,再固定第一个数,用左右双指针在后面寻找和为 0 的另外两个数。
推荐代码
推荐解法:双指针
时间复杂度: O(n^2)
空间复杂度: O(1)
核心思路: 先排序,再固定第一个数,用左右双指针在后面寻找和为 0 的另外两个数。
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
int n = nums.length;
Arrays.sort(nums);
List<List<Integer>> ans = new ArrayList<List<Integer>>();
// 枚举 a
for (int first = 0; first < n; ++first) {
// 需要和上一次枚举的数不相同
if (first > 0 && nums[first] == nums[first - 1]) {
continue;
}
// c 对应的指针初始指向数组的最右端
int third = n - 1;
int target = -nums[first];
// 枚举 b
for (int second = first + 1; second < n; ++second) {
// 需要和上一次枚举的数不相同
if (second > first + 1 && nums[second] == nums[second - 1]) {
continue;
}
// 需要保证 b 的指针在 c 的指针的左侧
while (second < third && nums[second] + nums[third] > target) {
--third;
}
// 如果指针重合,随着 b 后续的增加
// 就不会有满足 a+b+c=0 并且 b<c 的 c 了,可以退出循环
if (second == third) {
break;
}
if (nums[second] + nums[third] == target) {
List<Integer> list = new ArrayList<Integer>();
list.add(nums[first]);
list.add(nums[second]);
list.add(nums[third]);
ans.add(list);
}
}
}
return ans;
}
}结构化讲解
面试时怎么讲
开场思路
这题我会先排序,然后固定第一个数,把剩下部分转成有序数组里的两数之和问题。
核心过程
- 先排序,让双指针具备单调移动的基础。
- 外层枚举第一个数
nums[i],并跳过重复的起点。 - 内层用
left和right夹逼:和小了左移,大了右移,等于 0 就收集答案。 - 每次命中后继续跳过左右两侧的重复值,保证结果不重复。
复杂度总结
时间复杂度 O(n^2),空间复杂度 O(1),不计答案存储。
面试补一句:这题真正要记住的是“排序 + 去重”,不是只记住双指针三个分支。
核心思路
三数之和的关键有两层:先把数组排序,把问题变成“有序数组里找两数和”;再在遍历过程中主动跳过重复值,保证结果不重不漏。
步骤讲解
1
先排序,把搜索空间变成有序
排序后,三元组中的后两个数就可以用左右双指针夹逼,而不需要暴力枚举所有组合。
为什么这样做:双指针成立的前提是数组有序,这样和太大时右指针左移、和太小时左指针右移才有意义。
对应代码提示:Arrays.sort(nums);
2
依次固定第一个数
让 i 从左到右枚举三元组的第一个数,后面的两个数只在 i 右侧寻找。
为什么这样做:这样可以把三数之和稳定拆成“固定一个数 + 两数之和”的模板。
对应代码提示:for (int i = 0; i < nums.length - 2; i++) { ... }
3
左右夹逼寻找剩下两个数
令 left = i + 1、right = n - 1,计算三数之和;过大就缩右边,过小就扩左边,正好为 0 就记录答案。
为什么这样做:排序后,每次移动一个指针都能明确让总和朝目标靠近。
对应代码提示:if (sum < 0) left++; else if (sum > 0) right--; else answer.add(...);
4
在外层和内层都跳过重复值
固定第一个数时要跳过和前一个相同的值;找到一组答案后,左右指针也要越过相同元素。
为什么这样做:题目要的是不重复三元组,重复值如果不处理,会得到多份相同答案。
对应代码提示:while (left < right && nums[left] == nums[left - 1]) left++;
易错点
没排序就直接双指针
数组无序时,左右移动无法稳定判断和会变大还是变小,双指针逻辑就失效了。
正确理解:必须先排序,再做固定一个数 + 双指针夹逼。
只跳过外层重复,没跳过内层重复
即使 i 不重复,left 和 right 也可能反复落在同样的值上,导致重复三元组。
正确理解:记录答案后,继续移动左右指针并跳过相邻重复元素。
把返回条件写成是否存在
这题要求返回所有不重复三元组,不是找到一组就结束。
正确理解:命中后先记录结果,再继续移动指针搜索后续答案。
复杂度与适用判断
时间复杂度:O(n^2)
空间复杂度:O(1)
比其他方案更好在哪里:比三重循环暴力枚举少了一层搜索,把复杂度从
O(n^3) 降到 O(n^2)。适用判断:题目涉及有序数组、多数求和和去重组合时,优先考虑排序后再做双指针。
额外提醒
- 排序不是附属步骤,而是把问题降维成双指针模板的核心前提。
- 三数之和最容易丢分的地方通常不是指针移动,而是去重细节。
动画演示
如果你还没完全看懂,可以展开动画演示。
双指针动画演示
想看这段解法是怎么一步步运行的,可以展开动画继续观察。
双指针动画演示
想看这段解法是怎么一步步运行的,可以展开动画继续观察。
当前状态: 就绪
当前步骤: 1 / 0
三数之和双指针算法可视化
数组长度: 6
输入数组
-1
0
1
2
-1
-4
最终结果
暂无结果
动画演示仅在桌面端提供,移动端请优先阅读推荐代码与结构化讲解。