问题描述:
给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
解题思路:
- 和两数之和不同的是,三数之和要求nums[left] + nums[right] = - nums[i], 而不是nums[left] + nums[right] = 0,但是思路是类似的, 多一层 nums[i] 数组遍历就可以!
- 本题建议用双指针法,因为要返回结果的是不重复的数组,哈希法也可以,但是去重比较麻烦!!!用双指针法要对i、left、right去重,左右指针遇到和前一个值相同的情况选择直接跳过。
- 如何移动指针left 和right??
- 当 nums[left] + nums[right] > - nums[i] 时,就说明此时 nums[left] + nums[right] 大了,因为数组是排序后的,所以right下标就应该向左移动,这样才能 让nums[left] + nums[right] 小一些。
- 反之,nums[left] + nums[right] < - nums[i],left下标就应该向右移动,这样才能 让nums[left] + nums[right] 大一些。这点与两数之和的思路是类似的。
- 具体步骤:
- 首先,代码通过Arrays.sort(nums)将输入的整数数组进行排序,这是解决类似问题的常用步骤,可以帮助简化后续的操作。
- 初始化一个空的List,用于存储符合条件的三元组结果。
- 外层循环遍历整数数组nums,其中i表示当前固定的数,从0开始向后遍历。
- 在每次外层循环中,初始化两个指针left和right,分别指向i后一位的起始位置和数组末尾位置。
- 如果当前固定的数nums[i]大于0,则直接返回结果,因为三数之和不可能为0。
- 如果i大于0且当前的数与前一个数相同,则跳过本次循环,避免重复计算相同的三元组。
- 内层循环使用双指针法,不断向中间靠拢,寻找满足三数之和为0的组合。
- 如果找到满足条件的三元组,将其加入结果集中,并进行去重处理:
去除左指针右侧与当前左指针相同的重复元素;
去除右指针左侧与当前右指针相同的重复元素。 - 最后,返回结果集。
代码示例:
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
Arrays.sort(nums);
List<List<Integer>> result = new ArrayList<>();
//HashMap<Integer,Integer> map = new HashMap<>();
//for(int i = 0; i < nums.length; i++) map.put(nums[i], i);
for(int i = 0; i < nums.length; i++) {
int left = i + 1, right = nums.length - 1;
if (nums[i] > 0) return result;
//去重
if(i > 0 && nums[i] == nums[i-1]) continue;
while(left < right){
int temp = nums[left] + nums[right];
if(temp + nums[i] < 0) left++;
else if(temp + nums[i] > 0) right--;
else {
result.add(Arrays.asList(nums[left], nums[right], nums[i]));
//去重
while(left < right && nums[left] == nums[left + 1]) left++;
while(left < right && nums[right] == nums[right - 1]) right--;
left++;
right--;
}
}
}
return result;
}
}
- 时间复杂度分析
代码的时间复杂度是O(nlogn),其中 N 是输入整数数组的长度。具体来说,外层循环从 0 到 N-1 遍历每个元素,时间复杂度为 O(n)。内层循环使用双指针法在当前元素后面的区间寻找满足条件的三元组,时间复杂度为 O(n)。因此,总时间复杂度为 O(n^2)。
需要注意的是,排序的时间复杂度为 O(nlogn),但由于 n^2 远大于 nlogn,故总时间复杂度仍为O(n2)。
补充与总结:
- 本题的一个重点是对结果去重。
if(i > 0 && nums[i] == nums[i-1]) continue;
是为了给第一个索引 i 去重!nums[i]一样的情况下,求nums[left] + nums[right] = - nums[i]的组合,结果必然重复!
while(left < right && nums[left] == nums[left + 1]) left++;
while(left < right && nums[right] == nums[right - 1]) right--;
同理,是为了给第二、 三两个索引left、right去重!!
- 可以先做一下第1题:两数之和!