题目链接
https://leetcode-cn.com/problems/3sum/
题意
很简单,就是给出一个数组,3个数一组,找到所有和为0的组。并且要求不能重复。或者说找其中3个数其和为0,找出所有的组合。
题解
很好的一个题了,博主做了3次,前两次全部TLE,最后才AC了,但是整体思路差不多,就看能不能想到双指针的操作。前两次就是太菜了。
先说一个比较简单的思路,双指针的思路也有很大程度的重合,也是博主前两次TLE的思路:直接爆搜+剪枝。首先数组是要排序的,那么我们从最小的数开始暴搜,作为第一个数。然后搜第二个、第三个。如果不剪枝的话绝壁是超时的。比较容易想到的剪枝就是和如果大于0的时候可以直接剪掉了。我们可以当nums[i] > 0、nums[i]+nums[j] > 0分别剪去。然后就是第三个数k的范围。直接O(N^3)的搜索肯定不行了,那么k应该至少从0开始。因为i与j两者的和必须要<=0才有意义,否则就不存在第三个数了。所以可以预先搜索0或者第一个大于0的数,作为k的开始。预先搜索也略坑,我们直接把k当最大值反向搜就可以了。
其次,一个坑就是不能要求重复。如果我们直接搜而不考虑去重的情况会这样,例如数据:
-1,-1,0,1
那么会出现两组[-1,0,1]这就是重复了。因为我们已经排序,所以去重的方法也非常简单,例如i,当i > 0时,如果nums[i] = nums[i-1],我们可以continue,跳过这次搜索,直接i++。这样就完成了去重。不能利用set去重,这样就把重复的数变成一个数了,对于:
-1,-1,2
这种类似的数据而言是非常致命的,至少少算了一组。
上面就是博主TLE两次的算法了,很暴力。实际上这样的算法时间复杂度可以说是O(N^3)了,虽然看起来剪枝减去了很多,但是TLE还是妥妥的。而用双指针的话就能将复杂度减为O(n^2),并且仍然能使用上面的剪枝。仍然是遍历i,但是j与k设定为两个指针,j = i+1,k = len-1。这样,如果三数的和为0,那么就不多说了,j++以及k--,同时加入解集。当三数的和大于0,说明k应该减小,小于0则是j增大。这样就能在O(n^2)内实现找到所有解。
当然,对于i、j仍然可以判断大于0来进行一次剪枝。同时也应该考虑去重的情况。
Java 代码
import java.util.*;
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> ans = new ArrayList<>();
int len = nums.length;
if(len < 2) return ans;
Arrays.sort(nums);
for(int i = 0;i < len-2;i++){
if(nums[i] > 0) break;
if(i > 0){
if(nums[i] == nums[i-1]) continue;
}
int j = i+1;
int k = len-1;
while(j < k){
if(nums[i] + nums[j] + nums[k] == 0){
List<Integer> ad = new ArrayList<>();
ad.add(nums[i]);
ad.add(nums[j]);
ad.add(nums[k]);
ans.add(ad);
k--;
while(k > j && nums[k] == nums[k+1]) k--;
j++;
while(j < k && nums[j] == nums[j-1]) j++;
}else{
if(nums[i] + nums[j] + nums[k] > 0){
k--;
}else{
j++;
}
}
}
}
return ans;
}
}