这段代码是解决 LeetCode 15. 三数之和 问题的经典排序 + 双指针解法,核心目标是在整数数组中找到所有不重复的三元组(a, b, c),使得 a + b + c = 0。该解法通过排序简化查找逻辑,结合双指针优化效率,并通过严格去重避免重复结果,时间复杂度为 O (n²),空间复杂度为 O (log n)(主要来自排序)。
一、问题理解
问题要求
给定一个整数数组 nums,返回所有和为 0 的三元组 [nums[i], nums[j], nums[k]],满足:
- i、j、k 是不同的索引(i ≠ j、i ≠ k、j ≠ k);
- 结果中不能包含重复的三元组 (如
[-1, 0, 1]和[0, -1, 1]视为相同,需去重)。
二、核心思路:排序 + 双指针 + 去重
暴力解法(三重循环枚举所有三元组)时间复杂度为 O (n³),且需要额外去重,效率极低。该解法通过以下优化实现高效求解:
-
排序:先对数组排序,好处有二:
- 方便利用双指针缩小查找范围(根据和的大小移动指针);
- 让相同元素相邻,便于去重(避免重复三元组)。
-
固定一元素 + 双指针找另外两元素:
- 固定第一个元素
nums[i](i 从 0 到 n-3); - 用左指针
left = i+1、右指针right = n-1寻找另外两个元素,使得三者和为 0。
- 固定第一个元素
-
根据和调整指针:
- 若
nums[i] + nums[left] + nums[right] == 0:找到符合条件的三元组,加入结果; - 若和 < 0:总和偏小,左指针右移(增大数值);
- 若和 > 0:总和偏大,右指针左移(减小数值)。
- 若
-
去重逻辑:对固定元素 i、左指针 left、右指针 right 分别去重,避免重复三元组。
三、代码逐行解析
java
运行
java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
// 1. 初始化结果列表(存储所有符合条件的三元组)
List<List<Integer>> ans = new ArrayList<>();
// 2. 边界处理:数组为空或长度不足3,直接返回空列表
if (nums == null || nums.length < 3) {
return ans;
}
// 3. 对数组排序(核心前提:方便双指针查找和去重)
Arrays.sort(nums);
// 4. 固定第一个元素i,遍历数组(i最大为n-3,确保left和right有位置)
for (int i = 0; i < nums.length; i++) {
// 4.1 去重i:若当前元素与前一个元素相同,跳过(避免重复三元组)
// 注意i>0:防止i=0时越界
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
// 4.2 初始化双指针:left从i+1开始(避免重复索引),right从末尾开始
int left = i + 1;
int right = nums.length - 1;
// 4.3 双指针循环:left < right(确保两指针不重叠)
while (left < right) {
// 计算当前三元组的和
int sum = nums[i] + nums[left] + nums[right];
if (sum == 0) {
// 5. 找到符合条件的三元组,加入结果列表
ans.add(new ArrayList<>(Arrays.asList(nums[i], nums[left], nums[right])));
// 5.1 去重left:跳过与当前left相同的元素(避免重复三元组)
// 注意left < right:防止越界
while (left < right && nums[left] == nums[left + 1]) {
left++;
}
// 5.2 去重right:跳过与当前right相同的元素
while (left < right && nums[right] == nums[right - 1]) {
right--;
}
// 5.3 移动双指针(找到一组后必须移动,避免死循环)
left++;
right--;
} else if (sum < 0) {
// 6. 和偏小:左指针右移(增大数值,让总和更接近0)
left++;
} else {
// 7. 和偏大:右指针左移(减小数值,让总和更接近0)
right--;
}
}
}
// 8. 返回所有符合条件的三元组
return ans;
}
}
四、实例演示(直观理解过程)
以测试用例 nums = [-1, 0, 1, 2, -1, -4] 为例,演示执行过程:
步骤 1:排序数组
排序后 nums = [-4, -1, -1, 0, 1, 2](相同元素相邻,方便去重)。
步骤 2:遍历固定元素 i
i=0(nums[i] = -4):
- left=1(-1),right=5(2),sum = -4 + (-1) + 2 = -3 < 0 → left 右移至 2(-1);
- sum = -4 + (-1) + 2 = -3 < 0 → left 右移至 3(0);
- sum = -4 + 0 + 2 = -2 < 0 → left 右移至 4(1);
- sum = -4 + 1 + 2 = -1 < 0 → left 右移至 5,此时 left 不小于 right,循环结束(无符合条件的三元组)。
i=1(nums[i] = -1):
- 去重检查:i=1 > 0,nums [1](-1)≠ nums [0](-4)→ 不跳过;
- left=2(-1),right=5(2),sum = -1 + (-1) + 2 = 0 → 符合条件,加入结果
[-1, -1, 2];- 去重 left:nums [2](-1)== nums [3](0)?否 → 不移动;
- 去重 right:nums [5](2)== nums [4](1)?否 → 不移动;
- 移动指针:left=3,right=4;
- sum = -1 + 0 + 1 = 0 → 符合条件,加入结果
[-1, 0, 1];- 去重 left:nums [3](0)== nums [4](1)?否 → 不移动;
- 去重 right:nums [4](1)== nums [3](0)?否 → 不移动;
- 移动指针:left=4,right=3 → 循环结束。
i=2(nums[i] = -1):
- 去重检查:i=2 > 0,nums [2](-1)== nums [1](-1)→ 跳过(避免重复三元组)。
i=3(nums[i] = 0):
- left=4(1),right=5(2),sum = 0 + 1 + 2 = 3 > 0 → right 左移至 4,此时 left 不小于 right,循环结束。
i≥4:数组长度不足(left = i+1 会超过 right),循环结束。
最终结果
[[-1, -1, 2], [-1, 0, 1]],无重复三元组,符合要求。
五、关键细节:去重逻辑详解
去重是该题的核心难点,代码通过三层去重确保结果唯一:
-
固定元素 i 的去重 :
if (i > 0 && nums[i] == nums[i - 1]) continue;- 原因:若 nums [i] 与 nums [i-1] 相同,那么以 i 为第一个元素的三元组,必然和以 i-1 为第一个元素的三元组重复(因为数组已排序,后续元素相同)。
- 注意 i>0:防止 i=0 时访问 nums [-1] 越界。
-
左指针 left 的去重 :
while (left < right && nums[left] == nums[left + 1]) left++;- 时机:仅在找到一个符合条件的三元组后执行(避免漏解)。
- 原因:若 nums [left] 与 nums [left+1] 相同,移动 left 可跳过重复元素,避免同一三元组被多次加入。
- 注意 left < right:防止 left+1 越界。
-
右指针 right 的去重 :
while (left < right && nums[right] == nums[right - 1]) right--;- 逻辑同 left 去重,确保 right 侧无重复元素。
六、复杂度分析
- 时间复杂度:O(n²)。排序耗时 O (n log n),外层循环遍历 i 耗时 O (n),内层双指针遍历耗时 O (n),整体由 O (n²) 主导。
- 空间复杂度:O(log n)。主要来自排序的空间开销(Java Arrays.sort () 对基本类型使用双轴快排,空间复杂度为 O (log n)),结果列表的空间不计入(属于输出要求)。
七、总结
该解法的核心是 **"排序 + 双指针 + 分层去重"**:通过排序简化查找和去重,双指针将内层查找从 O (n²) 优化为 O (n),分层去重确保结果无重复。这种思路不仅适用于三数之和,还可迁移到 "四数之和" 等类似的 "n 数之和" 问题,是面试中高频考察的经典算法思想。