一、题目描述
https://leetcode-cn.com/problems/3sum/
给定一个包含 n 个整数的数组 nums
,判断 nums
中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。
例如, 给定数组 nums = [-1, 0, 1, 2, -1, -4], 满足要求的三元组集合为: [ [-1, 0, 1], [-1, -1, 2] ]
二、解题思路
解此题的主要想法是先对数组排序,再依此固定一个数,然后再去找另外两个数,使得三个数之和等于0。
(1)在固定一个数后,找另外两个数的过程中要使得找数的时间尽可能的少,所以可以设置两个指针l、r分别从左右两边向中间找。
(2)因为题目的要求是不可以包含重复的三元组,所以 i 固定数,l、r在找数的过程中要把这些重复的答案去掉。
如:在 l<r 的情况下,如果 l 和 l+1 指向的数相等,则此时应该跳过 l 至 l+1,r 同理;
i 在固定数的过程中亦是如此,如果 i 指向的值等于 i-1(上一次固定的数),则应该跳过这种情况;
(3)剪枝优化:因为数组是已经排序的,如果如果遍历到 i 指向的值大于0时,则肯定不存在 i 和后面的两个数相加等于0的情况。
代码如下:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
public class ThreeSum {
public static List<List<Integer>> threeSum(int[] nums){
if(nums == null)
return null;
if(nums.length<3)
return new ArrayList<>();
Arrays.parallelSort(nums); //对数组nums进行排序
List<List<Integer>> ls = new ArrayList<>();
//让i从数组下标为0开始
for(int i=0;i<nums.length-2;i++) { //因为每次都会操作i和i后面的另外两个数,所以只遍历至倒数第3个数即可
if(nums[i]>0)
break; //因为数组已经有序,如果遍历到第i个元素为正的话,那么它后面的元素的值都为正,不存在和为0的情况
//跳过可能重复的答案,如下标i、i-1指向的值可能相等,也就是说当前要处理数据在上一次已经处理过了。
if(i==0 || (i>0 && nums[i] != nums[i-1])) {
//l从i的后一个元素开始
int l = i+1;
//r从数组的最后一个数(最大的一个数)往前
int r = nums.length - 1;
while(l<r) {
if(nums[i] + nums[l] + nums[r] == 0) {
ls.add(Arrays.asList(nums[i],nums[l],nums[r]));
while(l<r && nums[l] == nums[l+1])
l++; //去掉重复的结果,注意l++后的l指向的是元素的值和nums[l]是相等,所以下面还需要一次l++
while(l<r && nums[r] == nums[r-1])
r--; //同上
l++;
r--;
}else if(nums[i]+nums[l]+nums[r]<0) {
//此时i是固定的,r指向的是数组中最大的元素(相对),所以要让三数之和去等于0,需要增大l指向的元素
while(l<r && nums[i] == nums[l+1])
l++; //跳过相同的情况
l++;
}else { //三数之和大于0,必须减小r指向的元素
while(l<r && nums[r] == nums[r-1])
r--;
r--;
}
}
}
}//for
return ls;
}
public static void main(String[] args) {
int[] nums = new int[] {-1,0,1,2,-1,-4};
List<List<Integer>> ls = threeSum(nums);
for(List<Integer> it : ls) {
for(int item : it) {
System.out.print(item+" ");
}
System.out.println();
}
}
}