题目描述
给你一个整数数组 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] 。
注意,输出的顺序和三元组的顺序并不重要。
输入:nums = [0,1,1]
输出:[]
解释:唯一可能的三元组和不为 0
输入:nums = [0,0,0]
输出:[[0,0,0]]
解释:唯一可能的三元组和为 0 。
解题思路
参考网址
暴力求解(本文没有代码实现):
- 最容易想到的思路:三个for循环遍历找到和为0的数,并用list进行记录。
- 存在的问题:题目中说三元组不能重复!用上述方式返回的三元组可能有重复的,还需要再次去重。【题解说可以利用哈希表去重,可以考虑一下】
- 不可行处:时间复杂度太高,一定会超时 O ( n 3 ) O(n^3) O(n3),并且去重消耗了额外的空间
题解思路:
- 对于不重复问题
- 可以考虑将原来数据进行排序
- 得到类似a<=b<=c的结果,使得a+b+c=0(等号是由于题目中数组有重复元素,所以才是<=等号,类似于000)
- 解决了不重复问题仍然是三个for循环,耗费时间较长
- 但是在第一个for循环里面,实际是在找满足b+c=target的second和third,这是可以通过
双指针
实现- 当要枚举数组中的两个元素时,如果我们发现随着一个元素的递增,另一个元素是递减的,那么就可以利用双指针来实现
- 双指针使得枚举的时间复杂度从 O ( n 2 ) O(n^2) O(n2)降到了O(n)
- 因为左指针向右移动,右指针向左移动,两个指针一共移动的位置为O(n)
- 通过上述方式,排序的时间复杂度为O(nlogn),枚举的时间复杂度为 O ( n 2 ) O(n^2) O(n2),因为第一重for循环里面的复杂度为O(n),所以总的时间复杂度为 O ( n 2 ) O(n^2) O(n2)
代码实现
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
// a、b、c分别代表nums[first]、nums[second]、nums[third]对应的值
//first、second、third代表下标/指针
int length = nums.length;
List<List<Integer>> res = new ArrayList<List<Integer>>();
int first, second, third;//利用三个值进行遍历
//为了防止有重复的三元组,首先对数组进行排序,目的得到a<b<c的样式,使得a+b+c=0
Arrays.sort(nums);//将数组进行排序
//for循环遍历
for(first = 0; first < length; first++){
//需要枚举与上次不同的数,注意不要越界,即一定在first>0的情况下判断
//注意是nums[first-1]而不是nums[first--],first--是需要修改first的值的
if(first>0 && nums[first]==nums[first-1]){
continue;
}
//寻找两数组元素和为-nums[first]的元素,利用双指针求解
int target = -nums[first];
//双指针,双重for循环求得答案,目标b<c的样式
for(second = first+1; second<length; second++){
//需要枚举与上次不同的数
if(second>first+1 && nums[second]==nums[second-1]){
//枚举下一个second
continue;
}
//第三个指针从后往前遍历
third = length-1;
//b与c的加和应该为target,如果两者加和小于target,直接跳出循环
//因为在b不变的情况下,c从后往前是越来越小的
//这个if语句可以不写。不满足下面的while循环,直接跳出了while循环,去判断了b+c是否等于target
if(nums[second]+nums[third] < target){
//注意是continue,需要判断下一个second
continue;
}
//如果加和大于targe,c对应的指针左移
while(second < third && nums[second]+nums[third]>target){
third--;
}
//如果两个指针指向的位置相同,代表再也不会出现b<c的样式,应当退出循环
//若b=c时,满足b+c=target,这时这个数组中应该有两个相同的b或者c,指针secon和third肯定是不同的
//所以应当先判断该条件,再判断b+c是否等于target
if(second == third){
break;
}
//如果b+c=target,则找到满足目标条件的abc,需要写入list
if(nums[second] + nums[third] == target){
List<Integer> list = new ArrayList<Integer>();
list.add(nums[first]);
list.add(nums[second]);
list.add(nums[third]);
res.add(list);
}
}
}
return res;
}
}
代码优化
third = length-1;
的位置在第二个for循环之上(循环遍历second之上)
原因:当second往后遍历的时候,b的值越来越大,third对应的c应当比上一循环里的c要小,否则肯定达不到b+c=target的效果。
==双指针的右指针没必要每次都从数组尾部向前移动!==在上次移动后的位置的基础上向前移动即可!- 不判断
if(nums[second]+nums[third] < target)
,后面的while循环包含了判断的过程,不是主要原因,时间差不多3ms
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
// a、b、c分别代表nums[first]、nums[second]、nums[third]对应的值
//first、second、third代表下标/指针
int length = nums.length;
List<List<Integer>> res = new ArrayList<List<Integer>>();
int first, second, third;//利用三个值进行遍历
//为了防止有重复的三元组,首先对数组进行排序,目的得到a<b<c的样式,使得a+b+c=0
Arrays.sort(nums);//将数组进行排序
//for循环遍历
for(first = 0; first < length; first++){
//需要枚举与上次不同的数,注意不要越界,即一定在first>0的情况下判断
//注意是nums[first-1]而不是nums[first--],first--是需要修改first的值的
if(first>0 && nums[first]==nums[first-1]){
continue;
}
//寻找两数组元素和为-nums[first]的元素,利用双指针求解
int target = -nums[first];
//第三个指针从后往前遍历
//这个third写在第二个for循环之上,节省时间!
//原因,当second往后遍历的时候,b的值越来越大,third对应的c应当比上一循环里的c要小,否则肯定达不到b+c=target的效果。
third = length-1;
//双指针,双重for循环求得答案,目标b<c的样式
for(second = first+1; second<length; second++){
//需要枚举与上次不同的数
if(second>first+1 && nums[second]==nums[second-1]){
//枚举下一个second
continue;
}
//b与c的加和应该为target,如果两者加和小于target,直接跳出循环
//因为在b不变的情况下,c从后往前是越来越小的
//这个if语句可以不写。不满足下面的while循环,直接跳出了while循环,去判断了b+c是否等于target
//不写节省时间!
// if(nums[second]+nums[third] < target){
// //注意是continue,需要判断下一个second
// continue;
// }
//如果加和大于targe,c对应的指针左移
while(second < third && nums[second]+nums[third]>target){
third--;
}
//如果两个指针指向的位置相同,代表再也不会出现b<c的样式,应当退出循环
//若b=c时,满足b+c=target,这时这个数组中应该有两个相同的b或者c,指针secon和third肯定是不同的
//所以应当先判断该条件,再判断b+c是否等于target
if(second == third){
break;
}
//如果b+c=target,则找到满足目标条件的abc,需要写入list
if(nums[second] + nums[third] == target){
List<Integer> list = new ArrayList<Integer>();
list.add(nums[first]);
list.add(nums[second]);
list.add(nums[third]);
res.add(list);
}
}
}
return res;
}
}
注意事项
continue
与break