三数之和
问题描述
给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请
你返回所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例
示例 1:
输入: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] 。
注意,输出的顺序和三元组的顺序并不重要。
示例 2:
输入:nums = [0,1,1]
输出:[]
解释:唯一可能的三元组和不为 0 。
示例 3:
输入:nums = [0,0,0]
输出:[[0,0,0]]
解释:唯一可能的三元组和为 0 。
代码实现
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> res = new ArrayList();
//特殊情况处理
if (nums == null || nums.length < 3) {
return res;
}
//对数组进行排序,排序后数组为升序
Arrays.sort(nums);
//设三元组中的三个元素为a、b、c; 则i指向元素a, left指向元素b, right指向元素c
for (int i = 0; i < nums.length; i++) {
//通过对数组的遍历及去重取不同的元素a
if (i > 0 && nums[i] == nums[i - 1]) {
//注意:此处的去重判断应为nums[i] == nums[i - 1]而不是nums[i] == nums[i + 1]!!!
continue;
}
//取元素b、c时采用双指针法
//由于对数组进行了排序,left右边的元素都大于left指向的元素值; right左边的元素都小于right指向的元素值
int left = i + 1;
int right = nums.length - 1;
//当left < right时循环移动双指针
while (left < right) {
int sum = nums[i] + nums[left] + nums[right];
if (sum < 0) {
//和小于0时将left向右移
left++;
} else if (sum > 0) {
//和大于0时将right向左移
right--;
} else {
//等于0时将该三元组加入结果集中
res.add(Arrays.asList(nums[i], nums[left], nums[right]));
//试探是否还有其他元素b、c满足条件
left ++;
right --;
//注意:此时要对元素b、c进行去重!!!
while (left < right && nums[left] == nums[left - 1]) {
left ++;
}
while (left < right && nums[right] == nums[right + 1]) {
right --;
}
}
}
}
return res;
}
}
疑难解答
-
为什么对a的去重判断应为nums[i] == nums[i - 1]而不是nums[i] == nums[i + 1]?
若对a的去重判断为nums[i] == nums[i + 1],那么由于满足条件的a元素不等于nums[i + 1]则求得的三元组中b元素一定不等于a元素。但符合题目要求的三元组中b元素有可能等于a元素。
-
为什么对a去重后对元素b、c也要去重?
因为仅仅保证元素a的值不同并不能确保三元组不重复。考虑数组[-2, 0, 0, 2, 2],当i = 0, left = 1, right = 4时取得符合条件的三元组【-2,0,2】。然而移动left和right指针后取得的三元组虽然满足题目条件但与上一个取得的三元组重复。
复杂度
时间复杂度
O(n^2)
空间复杂度
O(1)