题目
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例 1:
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
示例 2:
输入:nums = []
输出:[]
示例 3:
输入:nums = [0]
输出:[]
分析
这里本可以借鉴两数之和
的哈希法,通过判断0-(a+b)是否存在数组中来解答,但是由于题目要求的不能包含重复的三元组用这个方法实在很难去处理,所以这里就不推荐用哈希法了。
这里推荐排序+双指针法
来解决。先对数组进行排序,然后用一个指针i
对所有元素进行遍历,每一次遍历我们都有两个指针叫left
和right
,left一开始指向i+1
,right一开始指向数组最后一位,然后开始判断此时三个指针之和sum是否为0。
如果不是又分为两种情况,sum大于0,则让right左移一位,让sum减小;如果sum小于0,则让left右移一位,让sum增加。如果是找到了的话,就两边同时收缩。整个过程就是通过调整left和right指针来毕竟让sum逼近0,再注意一下去除重复情况即可。
动画如下:
代码
// 排序+双指针法(较优)
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
// 排序
Arrays.sort(nums);
for (int i = 0; i < nums.length; i++) {
// 首位大于0的直接返回
if (nums[i] > 0) {
return result;
}
// i的去重
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
int left = i + 1;
int right = nums.length - 1;
// 循环
while (right > left) {
// 计算此时的三数之和
int sum = nums[i] + nums[left] + nums[right];
// 三种情况
if (sum > 0) {
right--;
} else if (sum < 0) {
left++;
} else {
// 找到一种答案添加到结果集
result.add(Arrays.asList(nums[i], nums[left], nums[right]));
// 去重
while (right > left && nums[right] == nums[right - 1]) right--;
while (right > left && nums[left] == nums[left + 1]) left++;
// 找到一个答案后两边指针都收缩
right--;
left++;
}
}
}
return result;
}
时间复杂度:O(n^2)
延伸
这里顺便把四数之和也说了,思路也是差不多的,多了一个数其实就多加一层循环再去使用left和right算sum罢了,时间复杂度就变成O(n^3),如果有五数之和、六数之和也是一样的道理。
直接上代码:
public List<List<Integer>> fourSum(int[] nums, int target) {
List<List<Integer>> result = new ArrayList<>();
Arrays.sort(nums);
// 四数之和实际上是在三数之和上多加了一层循环
for (int i = 0; i < nums.length; i++) {
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
for (int j = i + 1; j < nums.length; j++) {
if (j > i + 1 && nums[j] == nums[j - 1]) {
continue;
}
int left = j + 1;
int right = nums.length - 1;
while (left < right) {
int sum = nums[i] + nums[j] + nums[left] + nums[right];
if (sum > target) {
right--;
} else if (sum < target) {
left ++;
} else {
result.add(Arrays.asList(nums[i], nums[j], nums[left], nums[right]));
// 去重
while (left < right && nums[right] == nums[right - 1]) right--;
while (left < right && nums[left + 1] == nums[left]) left++;
// 找到后左右指针各收缩一下
right--;
left++;
}
}
}
}
return result;
}