LeetCode0015 三数之和

问题描述

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。

注意:答案中不可以包含重复的三元组。

示例:

输入:nums = [-1, 0, 1, 2, -1, -4]
输出:[[-1. -1. 2], [-1, 0, -1]]

约束条件:

  1. 1.0 <= nums.length <= 3000
  2. -105 <= nums[i] <= 105

解题思路

问题一:如何去除重复解?

对数组进行排序,每次遍历时,如果和上次遍历的元素相等,则跳过,直到找到和上个元素不相等的元素。这样可以保证,同一个元素在一次遍历中只会选择一次。

问题二:如何遍历元素

首先想到的是暴力法,三层循环,在第三层循环中判断三数之和是否等于 0,若等于 0 则记录并开始下一轮循环;但是此种解法的复杂度为O(n^3),需要寻找更优解。

对于数组的双重循环,我们经常用双指针法的去优化。在此题目中,我们可以将暴力法中第二层和第三层循环转换为双指针方式。即可以将寻找 a + b + c = 0 转换为 寻找 b + c = -a。

算法过程

有长度为 length 的数组 nums,令指针 a 指向下标为 i 的元素,指针 b 指向下标为 i + 1 的元素,指针 c 指向下标为 length - 1 的元素。

  • 此时如果 nums[a] + nums[b] + nums[c] = 0, 则找到一组解。然后移动指针 b 和指针 c,继续寻找下一组解。
    • 指针 b 向后移动 1。为消除重复值,此时需要判断 nums[b] 和 nums[b - 1] 是否相等,若相等则继续后移,直到移动到 nums[b] 不等于 nums[b - 1] 或者 b >= c。
    • 同理,指针 c 向前移动 1。移动后需要判断 nums[c] 和 nums[c + 1] 是否相等,若相等则继续前移,直到 nums[c] 和 nums[c + 1] 不等 或 b >= c。
  • 此时如果 nums[a] + nums[b] + nums[c] > 0,即 -nums[a] < nums[b] + nums[c],此时明显 nums[c]的值太大,需要一个更小的值,才有可能使得 nums[b] 和 nums[c] 之和变小,此时,需要将指针 c 向前移动 1。
  • 此时如果 nums[a] + nums[b] + nums[c] < 0,即 -nums[a] > nums[b] + nums[c],此时明显 nums[b] 的值比较小,需要一个更大的值,才有可能使得 nums[b] 和 nums[c] 之和变大,此时,需要把指针 b 向后移动 1。

代码

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
public List<List<Integer>> threeSum(int[] nums) {
if (nums.length < 3) {
return Collections.emptyList();
}
// 先排序
Arrays.sort(nums);
List<List<Integer>> result = new ArrayList<>();
for (int a = 0, len = nums.length; a < len; a++) {
// 如果第一个元素就大于 0,一定不存在三数之和等于 0 的情况
if (nums[a] > 0) {
break;
}
if (a > 0 && nums[a] == nums[a - 1]) {
continue;
}
int b = a + 1;
int c = len - 1;
while (b < c) {
int sum = nums[a] + nums[b] + nums[c];
if (sum == 0) {
// 找到 a + b + c = 0 的三个数,记录
List<Integer> data = new ArrayList<>(3);
data.add(nums[a]);
data.add(nums[b]);
data.add(nums[c]);
result.add(data);
b++;
c--;
// 移动指针 b 和 指针 c,并去除重复值
while (b < c && nums[b] == nums[b - 1]) {
b++;
}
while (b < c && nums[c] == nums[c + 1]) {
c--;
}

} else if (sum > 0) {
// 和大于 0 , 说明 b + c 偏大,需要更小的数 ,指针 c 的向前移动
c--;
} else {
// 和小于 0,说明 b+ c 偏小,需要更大的数,指针 b 向后移动
b++;
}
}
}
return result;
}