给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
输入:nums = []
输出:[]
思考点1:如何做到不重复
a. 因为三元组要求不重复,我们第一感觉就是要对数组进行升序排序,否则分析会产生困难。
比如 -4...-1...-4...-1...5
。不排序的话,会有多种方式选择到-4,-1
,而且去重也不好想办法。
b. 基于a,数组排序后,因为可能存在连续的相同数值,比如[-1,-1,-1,-1,2]
。很明显,只有一个三元组[-1,-1,2]
符合要求。四个-1
元素选取哪两个呢?这引发出第二个思考点。
思考点2:连续相同的数值,如何选择
数组[-1, -1, -1, -1, 2]
的符合条件的三元组是[-1, -1, 2]
, 那两个-1是四个-1中的哪两个呢?按照方便起见我们选取最前面的那两个-1。这其实就等价于下述要求:
假如我们约定枚举第一个选中元素的循环叫做第一层循环,枚举第二个选中元素的循环叫做第二层循环,枚举第三个选中元素的循环叫做第三层循环。
a. 当前循环层开始的数组下标是上一循环层选中元素的下标+1。
b. 当前循环层遍历到的元素与前一元素相同时,舍弃。
拿上面的case来讲,假设第一个选中的元素selected1 = nums[0] = -1
时,第二个选中的元素selected2则从nums[1]开始算起。如果第二个元素选择nums[1], 即selected2 = nums[1]=-1
,则第三个元素就确定了,必须是2。从nums[2]开始找,直至找到元素2。
第二个元素能不能不选择nums[1]这个-1呢?而是nums[2]这个-1呢? 当然可以,但是算法实现就没有了逻辑了,我们基于b
的假设来实现算法的。
另外要注意到,从左向右遍历selected2时,由于要求三数之和为0,所以第三个数自然也会从右向左递减,这个特点可以使用双指针来实现。
public List<List<Integer>> threeSum(int[] nums) {
if (nums.length < 3) {
return new ArrayList<>();
}
List<List<Integer>> ans = new ArrayList<>();
Arrays.sort(nums);
// first表示第一个选中的元素对应的位置
for (int first = 0; first < nums.length - 2; first++) {
if (first > 0 && nums[first] == nums[first - 1]) {
continue;
}
int target = -nums[first];
// second表示第二个选中的元素对应的位置
for (int second = first + 1; second < nums.length - 1; second++) {
if (second > first + 1 && nums[second] == nums[second - 1]) {
continue;
}
// third表示第三个选中的元素对应的位置
// 这里可以优化,注意到每次枚举selected2时,selected3都从最右侧开始遍历
// 这是没必要的。可以将third的定义移动到当前for循环的外面,原因就是双指针
int third = nums.length - 1;
if (nums[second] + nums[third] < target) {
continue;
}
while (nums[second] + nums[third] > target && third > second) {
third--;
}
if (nums[second] + nums[third] == target && third > second) {
List<Integer> list = new ArrayList<>();
list.add(nums[first]);
list.add(nums[second]);
list.add(nums[third]);
ans.add(list);
}
}
}
return ans;
}