力扣hot100----15.三数之和(java版)
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、思路
方法一:暴力破解
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:排序 —— 为什么?
Arrays.sort(nums);为什么排序?
排序后,我们可以使用双指针技巧来高效查找两个数之和等于目标值。
排序后,可以轻松跳过重复元素,避免重复三元组。
排序是双指针法的前提。
时间复杂度:O(n log n)
步骤 2:外层循环固定第一个数 —— 为什么?
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:双指针找另外两个数 —— 为什么?
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>>
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[])
Arrays.sort(nums);知识点:
对原数组进行升序排序。
底层使用双轴快排(Dual-Pivot QuickSort),平均 O(n log n),不稳定排序。
原地排序,不额外占用空间。
(3) Arrays.asList(...)
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)
完整代码
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和rightwhile (left < right && nums[left] == nums[left + 1])left++;while (left < right && nums[right] == nums[right - 1])right--;//移动指针继续查找left++;right--;}}}return result;}
}