三数之和

题目描述:给你一个包含 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]
输出:[]

方法一:排序+双指针

  • 双指针法铺垫: 先将给定 nums 排序,复杂度为 O(NlogN)。
  • 双指针法思路: 固定 3 个指针中最左(最小)数字的指针 k,双指针 i,j 分设在数组索引 (k,len(nums)) 两端,通过双指针交替向中间移动,记录对于每个固定指针 k 的所有满足 nums[k] + nums[i] + nums[j] == 0 的 i,j 组合:
    1. 当 nums[k] > 0 时直接break跳出
      • 因为 nums[j] >= nums[i] >= nums[k] > 0,即 3 个数字都大于 0 ,在此固定指针 k 之后不可能再找到结果了。
    2. 当 k > 0且nums[k] == nums[k - 1]时即跳过此元素nums[k]
      • 因为已经将 nums[k - 1] 的所有组合加入到结果中,本次双指针搜索只会得到重复组合。
    3. i,j 分设在数组索引 (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],防止记录到重复组合。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
Arrays.sort(nums);
List<List<Integer>> res = new ArrayList<>();
for(int k = 0; k < nums.length - 2; k++){
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;
}
}

var threeSum = function (nums) {
const len = nums.length;
if (len < 3) return [];
nums.sort((a, b) => a - b);
const res = [];
for (let k = 0; k < len - 2; k++) {
if (nums[k] > 0) break;
if (k > 0 && nums[k] === nums[k - 1]) continue;
let i = k + 1,
j = len - 1;
while (i < j) {
const sum = nums[k] + nums[i] + nums[j];
if (sum < 0) {
i++;
continue
};
if (sum > 0) {
j--;
continue
};
res.push([nums[k], nums[i], nums[j]])
while (i < j && nums[i] === nums[++i]);
while (i < j && nums[j] === nums[--j]);
}
}
return res;
};

复杂度分析:

  • 时间复杂度 O(N^2):其中固定指针k循环复杂度 O(N),双指针 i,j 复杂度 O(N)。
  • 空间复杂度 O(1):指针使用常数大小的额外空间。

执行结果:通过

  • 执行用时:24 ms, 在所有 Java 提交中击败了60.87%的用户

  • 内存消耗:42.3 MB, 在所有 Java 提交中击败了57.20%的用户