一、题目描述
1.1 题目
-
三数之和
-
给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。注意:答案中 不可以包含重复 的三元组。
例如, 给定数组 nums = [-1, 0, 1, 2, -1, -4],满足要求的三元组集合为:
[
[-1, 0, 1],
[-1, -1, 2]
]
1.2 知识点
- 双指针
1.3 题目链接
二、解题思路
2.1 自研思路
这道题在半年之前做过一次,现在算来也是第三次见到了,思路比较清晰就是使用双指针来解决,将其从三数之和的 a+b+c=0 转换为 a+b=-c 即搜索 a+b 两数之和为定值 -c 的数字组合。但是关键点在于怎样选择 -c 这个固定值,开始的时候我想选择两个指针中间的位置,然后将两个指针的对应的值固定,来在其中间遍历查找 -c ,但是遇到了一些问题。所以改变思路后,选择使用最左边为 -c ,然后该 -c 下标的左边首点为左指针,而整个数组的最右边为右指针,然后双指针移动查找和为 -c 的数字组合,每次查找完即将 -c 固定值的指针左移一位,然后再进行上述查找。
对于算法优化主要在于:
- 去重策略的选择,尽量不要选择遍历整个结果数组来去重,复杂度太高;
- 剪枝优化,当 c 固定值和左指针及左指针左邻值相加就已经大于零或 c 固定值和右指针及右指针右邻值相加就已经小于零时,注定该轮双指针遍历不会得到结果,所以可以直接进行剪枝操作;
三、实现代码
3.1 自研实现
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
Arrays.sort(nums);
int left = 0, right = 0, target = 0;
for(int i = 0; i < nums.length-2; i++){
target = -nums[i];
left = i + 1;
right = nums.length-1;
// 剪枝 48ms -> 34ms ; 时间击败87% ; 内存击败94%
if(nums[i]+nums[left]+nums[left+1] > 0 || nums[i]+nums[right]+nums[right-1] < 0 )
continue;
while(left < right) {
if(nums[left]+nums[right] == target){
List<Integer> list = new ArrayList<Integer>();
list.add(nums[i]);
list.add(nums[left]);
list.add(nums[right]);
res.add(list);
// 去重并移动左指针
do left++;
while(left < right && left != right-1 && nums[left] == nums[left-1]);
// 去重并移动右指针
do right--;
while(left < right && left != right-1 && nums[right] == nums[right+1]);
} else if(nums[left]+nums[right] < target)
left++;
else
right--;
}
// 去重
while(i < nums.length-2 && nums[i] == nums[i+1]) i++;
}
return res;
}
}