题目描述
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例 1:
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
示例 2:
输入:nums = []
输出:[]
示例 3:
输入:nums = [0]
输出:[]
各种解法
1. 排序+双指针+两次去重
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
Arrays.sort(nums);
List<List<Integer>> res = new ArrayList<>();
// k是第一个数,保持不动
for(int k=0; k< nums.length-2; k++){
// 最小的都大于0,没有结果
if(nums[k] > 0) break;
// 和前一个数一样,有也是重复,不考虑
if(k>0 && nums[k]==nums[k-1]) continue;
// 双指针
int i=k+1, j=nums.length-1;
while(i<j){
int sum = nums[k]+nums[i]+nums[j];
// 和偏小,左指针向右
if(sum<0){
while (i<j && nums[i] == nums[++i]);
}else if(sum > 0){
while (i<j && nums[j] == nums[--j]);
}else{
res.add(new ArrayList<Integer>(Arrays.asList(nums[k],nums[i],nums[j])));
while(i<j && nums[i] == nums[++i]);
while (i<j && nums[j] == nums[--j]);
}
}
}
return res;
}
}
解题思路
- 排序:首先将数组进行排序,排完序有了大小关系,才能用双指针来找。如果和大了,右指针向左到一个较小的值;如果和小了,左指针向右到一个较大的值。
- 双指针:固定最左的指针最小,双指针在后面的区域之间移动。也就是固定1个数,然后就是用双指针求另外两个数的【两数之和】问题。
- 记录对于每个固定指针k的所有满足 nums[k] + nums[i] + nums[j] == 0 的 i,j 组合:
- 当 nums[k] > 0 时直接break跳出:因为 nums[j] >= nums[i] >= nums[k] > 0,即 3 个数字都大于 0,在此固定指针 k 之后不可能再找到结果了。
- 【第一次去重】:当 k > 0且nums[k] == nums[k - 1]时即跳过此元素nums[k]:因为已经将 nums[k - 1] 的所有组合加入到结果中,本次双指针搜索只会得到重复组合。
- i,j 分设在数组索引 (k, len(nums))(k,len(nums)) 两端,当i < j时循环计算s = nums[k] + nums[i] + nums[j],并按照以下规则执行双指针移动【第二次去重】:
- 当s < 0时,i += 1并跳过所有重复的nums[i];
- 当s > 0时,j -= 1并跳过所有重复的nums[j];
- 当s == 0时,记录组合[k, i, j]至res,执行i += 1和j -= 1并跳过所有重复的nums[i]和nums[j],防止记录到重复组合。
- 如果没有两次去重很可能超时。
- 排序O(nlogn),双指针O(n^2)
变形题
1.两数之和
分为两种情况
1.1 排好序的,双指针
排好序的两数之和,可以直接使用 双指针 解法,逻辑和上面的类似
int[] twoSum(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while (left < right) {
int sum = nums[left] + nums[right];
if (sum == target) {
return new int[]{left, right};
} else if (sum < target) {
left++; // 让 sum 大一点
} else if (sum > target) {
right--; // 让 sum 小一点
}
}
// 不存在这样两个数
return new int[]{-1, -1};
}
1.2 未排序的,HashMap一次遍历
如果要求时间复杂度为O(N),那么不可能再去排序了, 因为排序最优情况下都是 O(NlogN)。所以使用HashMap一次遍历,在遍历中记录信息。
class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<>();
for(int i=0; i<nums.length; i++){
// 存在一个就可以返回了
if(map.containsKey(target-nums[i])){
return new int[]{map.get(target-nums[i]),i};
}
// 当前还没有结果,把值和索引存入
map.put(nums[i], i);
}
return new int[0];
}
}
解题思路
- 在遍历的过程中,把值和自己的索引记录到HashMap中
- 如果HashMap中存在与自己相加为指定值的数,就成对输出
2. nSum问题
- 只要是排好序的数组,两数之和就可以使用双指针。
- 三数之和考虑对第一个数进行穷举,选出第一个后,对剩下的进行2sum解决。注意过程中的2次去重
- 扩展到nSum,那么如果n>2,就是递归计算(n-1)Sum的结果。第一个数穷举,后面的数(n-1)Sum。递归终止条件是2sum问题,可以直接用双指针解出来。