题目描述
给你一个整数数组 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 。
提示:
3 <= nums.length <= 3000
-10^5 <= nums[i] <= 10^5
题目分析
这题本人第一次做的时候想到使用哈希表存储nums[a] + nums[b]的值,以此简化从n3降低到n2,但是现实如此残酷,本题使用哈希表并不是最佳解决方案,会面临去重的麻烦问题,所以这题最佳的易懂的解决方案应该是使用双指针+去重的方法完成这道题目。
方法一:哈希表
思路:根据公式 nums[a] + nums[b] = -nums[c]
我们可以先使用双层for循环遍历数组,然后存储他们的和,然后再遍历一次数组,寻找等于 -nums[c] 的列表,然后把列表添加到结果集,但是我们要注意去重,这里可以使用Set集合进行去重。
代码:
public List<List<Integer>> hashMethod(int[] nums) {
//定义结果集
Set<List<Integer>> res = new HashSet<>();
//我们先定义一个 map 用于存储 nums[i] + nums[j] 的值
Map<Integer,List<List<Integer>>> map = new HashMap();
//双重for遍历获取值
for(int i = 0; i < nums.length; i ++) {
for(int j = i + 1; j < nums.length; j ++) {
//获取指定值在map中存储的List
List<List<Integer>> list = map.getOrDefault(nums[i] + nums[j],new ArrayList());
//这里将下标封装进List
List<Integer> list1 = new ArrayList(Arrays.asList(i,j)); //这里存入的是下标,因为后面要去重
list.add(list1);
//存入map中
map.put(nums[i] + nums[j],list);
}
}
//根据公式我们查找map中是否存在-(nums[i]),如果存在就将map中对应的list加入结果集中
for(int i = 0; i < nums.length; i ++) {
Set<List<Integer>> list = map.get(-nums[i]);
if(list == null) {
continue;
}
for(List<Integer> l : list) {
if(!l.contains(i)) {
//一个元素不可能出现多次
List<Integer> l2 = new ArrayList(l);
l2.add(i);
//将下标转换为nums中的元素
l2 = l2.stream().map((e)->nums[e]).collect(Collectors.toList());
//排序后存入Set集合中
l2.sort(Integer::compareTo);
res.add(l2);
}
}
}
//转换Set集合成List
return res.stream().collect(Collectors.toList());
}
上面这种方法是比较繁琐的,我们使用双指针来进行优化,可以将时间复杂度优化到O(n)
方法二:双指针+去重
思路: 首先我们对数组进行排序,我们可以定义一个指针遍历数组,然后用 left 和 right 指针来实现总和的计算,如果 sum > 0,就让 right --,如果 sum < 0 就让 left++。但是,在指针的去重上面,也有一些细节需要注意,比如 我们定义当 sum[i] == sum[i + 1] 就 continue 还是,当 nums[i] == nums[i - 1] 才 continue,这里,如果我们选择第一种方法,会导致出现一种情况,当三元组中有重复元素的时候可能会出现漏掉记录,例如 [0,0,0] 的时候会出现无法加入到结果集中。
代码:
public List<List<Integer>> dualPoint(int[] nums) {
//首先创建结果集
List<List<Integer>> res = new ArrayList();
//对数组进行排序
Arrays.sort(nums);
//如果排序后的第一个元素都大于0,那么就不会存在三元组和为0,直接返回空列表
if(nums[0] > 0) return new ArrayList();
//创建第一个指针
for(int i = 0; i < nums.length; i ++) {
//这里我们要注意去重方式
if(i > 0 && nums[i] == nums[i - 1]) continue;
//定义left和right指针
int left = i + 1, right = nums.length - 1;
//当left小于right的时候
while(left < right) {
//判断大小
int sum = nums[i] + nums[left] + nums[right];
if(sum > 0) {
//如果sum>0的话就让right--
right --;
} else if(sum < 0) {
//如果sum < 0就让left ++
left ++;
} else {
//当出现满足条件的三元组,就直接加入结果集中,这里和去重顺序不能颠倒,不然会出现遗漏
res.add(new ArrayList(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 --;
}
}
}
return res;
}
总结
这道题目相比前面的两数之和,难度有所提升,主要是我们要想到使用双指针来优化三元组的查找。不然直接使用哈希表会出现难以操作去重行为,而且时间复杂度还是比较高的。这题就到这里了,欢迎各位前来交流指点。