📌 题目描述
给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。
注意: 答案中不可以包含重复的三元组。
💡 示例:
输入:
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]。 注意,输出的顺序和三元组的顺序并不重要。
🧠 思路分析
我们在拿到这道题时,最直观的联想就是它的前置问题—— “两数之和” 。
思路1:
我们先固定一个数 i ,所求的target可以转变为 0 - nums[i]。对i之后的数组使用我们的两数之和即可。但是运行用例后发现我们计算出来居然有重复的答案,虽然位置不同但这是不行的。
例如[[-1,1,0],[0,-1,1]]
解决方案:
我们可以使用HashSet包裹住我们的排序后的List,利用set进行一个去重,但这种方式耗时很高。下面给出代码:
💻 核心代码实现
class Solution1 {
HashSet<List<Integer>> res;//去重HashSet
public List<List<Integer>> threeSum(int[] nums) {
res = new HashSet<>();
for (int i = 0; i < nums.length; i++) {
calTwoWordSum(i,nums);
}
return new ArrayList<>(res);// 将HashSet转为List返回结果
}
// 计算两数之和
public void calTwoWordSum(int start,int[] nums){
int target = 0 - nums[start];//新的Target
HashMap<Integer,Integer> cache = new HashMap<>();// 创建HashMap保存数据
for (int i = start+1; i < nums.length; i++) {
int nTarget = target - nums[i];
if(cache.containsKey(nTarget)){
List<Integer> list = new ArrayList<>();
list.add(nums[start]);
list.add(nums[i]);
list.add(nTarget);
Collections.sort(list);// 对list进行排序后放到HashSet中
res.add(list);
}
cache.put(nums[i],i);
}
}
}
思路2:
使用排序+双指针的形式
(这里建议一下大家,遇到排序好的数组问题,可以第一时间考虑二分法或左右双指针的方式,看看能不能求解)
- 先对nums进行排序
- 进行外层数字 i 的遍历
- 如果当前 nums[i] > 0 直接break,因为可以保证后续的节点无法实现 三数之和 == 0
- 如果当前 nums[i] == nums[i-1]直接continue,有效的去重
- 对剩余的数组进行左右双指针 nTarget = -num[i]
- 如果nums[left]+num[right] == nTarget,得到我们的值。再分别对左右指针进行去重判断后 left++,right --;
- 如果nums[left]+num[right] < nTarget, left++ (因为比目标值小,所有右移左指针)
- 如果nums[left]+num[right] > nTarget, right++(因为比目标值大,所有左移右指针)
💻 核心代码实现
class Solution {
List<List<Integer>> res; // 结果res
public List<List<Integer>> threeSum(int[] nums) {
res = new ArrayList<>();
Arrays.sort(nums); // 排序数组
for (int i = 0; i < nums.length -2; i++) {
if( nums[i] > 0){ // 减枝操作
break;
}
if(i > 0 && nums[i] == nums[i-1]){
continue; // 去重操作
}
calTwoWordSum(i,nums);
}
return res;
}
// 计算两数之和
public void calTwoWordSum(int start,int[] nums){
int target = 0 - nums[start];
int left = start + 1;
int right = nums.length -1;
while (left < right){
int sum = nums[left] + nums[right];
if(sum == target){
res.add(Arrays.asList(nums[start],nums[left],nums[right]));
//这一步很关键,是内部的去重,如果下一个数字相同,直接移动左右指针
while (left < right && nums[left] == nums[left+1]){
left++;
}
while (left < right && nums[right] == nums[right-1]){
right--;
}
//确认去重完后同时移动左右指针
left++;
right--;
}else if(sum > target){
right--;
}else {
left ++;
}
}
}
}