1、题目描述
给你一个整数数组
nums,判断是否存在三元组[nums[i], nums[j], nums[k]]满足i != j、i != k且j != k,同时还满足nums[i] + nums[j] + nums[k] == 0。请你返回所有和为0且不重复的三元组。**注意:**答案中不可以包含重复的三元组。
示例 1:
输入:nums = [-1,0,1,2,-1,-4] 输出:[[-1,-1,2],[-1,0,1]] 解释: nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。 nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。 nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。 不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。 注意,输出的顺序和三元组的顺序并不重要。示例 2:
输入:nums = [0,1,1] 输出:[] 解释:唯一可能的三元组和不为 0 。示例 3:
输入:nums = [0,0,0] 输出:[[0,0,0]] 解释:唯一可能的三元组和为 0 。
2、思路
方法一:暴力破解
java
class Solution{
public List<List<Integer>> threeSum(int[] nums){
Set<List<Integer>> resultSet = new HashSet<>();//使用Set自动去重
//三重循环:i,j,k互不相同
for(int i = 0;i<nums.length -2;i++){
for(int j = i+1;j<nums.length-1;j++){
for(int k = j+ 1;k<nums.length;k++){
if(nums[i]+nums[j]+nums[k] == 0){
//创建一个三元组,排序后加入Set(避免[0,-1,1]和[-1,0,-1]被认为不同)
List<Integer> triplet = Arrays.asList(nums[i],nums[j],nums[k]);
Collections.sort(triplet);//排序保证顺序一致
resultSet.add(triplet);
}
}
}
}
//转成List返回
return new ArrayList<>(resultSet);
}
}
/**Arrays.asList(...)
将一组元素(或一个数组)快速包装成一个固定大小的 List,便于临时使用或传递给需要集合参数的方法。
*/
**注意:**这种暴力破解的方法是可以的,但是在算法或者数据规模较大的情况下,会超出时间限制,因为它的时间复杂度是O(n^3),空间复杂度为O(1)
方法二:排序 + 双指针(Two Pointers)
时间复杂度:O(n²)
空间复杂度:O(1)(不考虑结果存储空间)
步骤 1:排序 ------ 为什么?
java
Arrays.sort(nums);
为什么排序?
-
排序后,我们可以使用双指针技巧来高效查找两个数之和等于目标值。
-
排序后,可以轻松跳过重复元素,避免重复三元组。
-
排序是双指针法的前提。
时间复杂度:O(n log n)
步骤 2:外层循环固定第一个数 ------ 为什么?
java
for (int i = 0; i < nums.length - 2; i++) {
if (i > 0 && nums[i] == nums[i - 1]) continue; // 跳过重复
...
}
为什么从 0 到 len-2?
-
因为要留出两个位置给***
left*** 和*right* 指针。 -
边界:
i < nums.length - 2
为什么跳过重复?
- 如果***
nums[i] == nums[i-1]***,说明以这个数开头的组合我们已经处理过了,跳过避免重复三元组。
步骤 3:双指针找另外两个数 ------ 为什么?
java
int left = i + 1, right = nums.length - 1;
while (left < right) {
int sum = nums[i] + nums[left] + nums[right];
if (sum < 0) left++;
else if (sum > 0) right--;
else {
// 找到一组解
list.add(Arrays.asList(nums[i], nums[left], nums[right]));
// 跳过重复
while (left < right && nums[left] == nums[left + 1]) left++;
while (left < right && nums[right] == nums[right - 1]) right--;
left++;
right--;
}
}
为什么双指针能工作?
-
数组已排序,所以:
-
如果当前三数之和 太小 → 需要更大的数 →
left++ -
如果当前三数之和 太大 → 需要更小的数 →
right-- -
如果 等于 0 → 找到答案,记录并移动双指针继续找
-
为什么移动指针时还要跳过重复?
-
例如:
[-2, 0, 0, 2, 2],当i=0,left=1,right=4,找到[-2,0,2] -
如果不跳过,
left=2,right=3又会找到相同的[-2,0,2]→ 重复!
边界条件(Corner Cases)
| 输入 | 说明 | 处理方式 |
|---|---|---|
nums.length < 3 |
不足三个数 | 直接返回空列表 |
全是 0:[0,0,0,0] |
只能有一个 [0,0,0] |
通过跳过重复处理 |
| 全是正数或负数 | 不可能和为 0 | 双指针自然不会找到解 |
| 有重复元素 | 如 [-1,-1,0,1] |
通过 i > 0 && nums[i]==nums[i-1] 跳过 |
Java 数据结构与算法知识点详解
(1) List<List<Integer>>
java
List<List<Integer>> result = new ArrayList<>();
知识点:
-
Java 泛型:
List<List<Integer>>表示"列表的列表",每个元素是一个List<Integer>。 -
ArrayList是***List***接口的常用实现,支持动态扩容。 -
添加元素:
result.add(Arrays.asList(a, b, c))------ 快速创建不可变列表。
(2)Arrays.sort(int[])
java
Arrays.sort(nums);
知识点:
-
对原数组进行升序排序。
-
底层使用双轴快排(Dual-Pivot QuickSort),平均 O(n log n),不稳定排序。
-
原地排序,不额外占用空间。
(3) Arrays.asList(...)
java
list.add(Arrays.asList(nums[i], nums[left], nums[right]));
知识点:
-
快速将多个元素包装成
List。 -
返回的是固定大小的列表 (基于数组),不能
add/remove,但这里只是用于添加到外层列表,没问题。 -
如果需要可变列表,可以用***
new ArrayList<>(Arrays.asList(...))***,但没必要。
(4)双指针(Two Pointers)算法
核心思想:
-
在有序数组中,用两个指针从两端向中间逼近,根据条件移动指针。
-
常用于:
-
两数之和
-
三数之和
-
最接近的三数之和
-
盛最多水的容器
-
优势:
-
时间复杂度从 O(n²) 降到 O(n)(在固定第一个数的前提下)
-
空间复杂度 O(1)
完整代码
java
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
//边界:不足三个数
if (nums == null || nums.length < 3) {
return result;
}
//1、排序
Arrays.sort(nums);
//2、遍历一个数
for (int i = 0; i < nums.length - 2; i++) {
//跳过重复的第一个数
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
int left = i + 1;
int right = nums.length - 1;
//3、双指针找另外两个数
while (left < right) {
int sum = nums[i] + nums[left] + nums[right];
if (sum < 0) {
left++;//和太小,左指针右移
} else if (sum > 0) {
right--;//和太小,右指针左移
} else {
//找到一组解
result.add(Arrays.asList(nums[i], nums[left], nums[right]));
//跳过重复的left和right
while (left < right && nums[left] == nums[left + 1])
left++;
while (left < right && nums[right] == nums[right - 1])
right--;
//移动指针继续查找
left++;
right--;
}
}
}
return result;
}
}