15. 三数之和

本文最后更新于:25 天前

15. 三数之和

题目描述

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。

请你返回所有和为 0 且不重复的三元组。

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

示例 1:

1
2
3
4
5
6
7
8
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。

示例 2:

1
2
3
输入:nums = [0,1,1]
输出:[]
解释:唯一可能的三元组和不为 0 。

示例 3:

1
2
3
输入:nums = [0,0,0]
输出:[[0,0,0]]
解释:唯一可能的三元组和为 0 。

提示:

  • 3 <= nums.length <= 3000
  • -10^5 <= nums[i] <= 10^5

题解

解法一:暴力枚举 + 去重

最容易想到的方法,直接枚举三个数的组合,当和为 0 时,将当前组合排序,然后遍历结果集,如果当前结果集中没有这个组合则加入结果集。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
std::vector<std::vector<int>> threeSum(std::vector<int>& nums) {
std::vector<std::vector<int>> res;
sort(nums.begin(), nums.end());
for (int i = 0; i < nums.size() - 2; i++) {
if (i > 0 && nums[i] == nums[i - 1]) continue;
for (int j = i + 1; j < nums.size() - 1; j++) {
if (j > i + 1 && nums[j] == nums[j - 1]) continue;
for (int k = j + 1; k < nums.size(); k++) {
if (nums[i] + nums[j] + nums[k] == 0) {
res.push_back({nums[i], nums[j], nums[k]});
break;
}
}
}
}
return res;
}

但是时间复杂度为 $O(n^3)$,提交后显示超时。

解法二:排序 + 双指针

解法一在去重过程需要先排序,不妨在最开始先进行排序。

当排序后,我们在遍历三元组时,当下标移动时遇到相同元组,直接跳过即可,避免了每次向结果集查找去重的操作。

遍历三元组时,必然先固定一个元素,nums[i],然后继续向后遍历 nums[j] 和 nums[k]。但是这样一次枚举就会导致时间复杂度过高。

可以发现,如果我们固定了前两重循环枚举到的元素 a 和 b,那么只有唯一的 c 满足 a+b+c=0。当第二重循环往后枚举一个元素 b′ 时,由于 b′>b,那么满足 a+b′+c′=0 的 c′ 一定有 c′ < c ,即 c′ 在数组中一定出现在 c 的左侧。也就是说,我们可以从小到大枚举 b,同时从大到小枚举 c,即第二重循环和第三重循环实际上是并列的关系。

有了这样的发现,我们就可以保持第二重循环不变,而将第三重循环变成一个从数组最右端开始向左移动的指针。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
std::vector<std::vector<int>> threeSum(std::vector<int>& nums) {
std::vector<std::vector<int>> res;
sort(nums.begin(), nums.end());
for (int i = 0; i < nums.size() - 2; i++) {
if (nums[i] > 0) break;
if (i > 0 && nums[i] == nums[i - 1]) continue;
int m = i + 1, n = nums.size() - 1;
while (m < n) {
if (nums[n] < 0) break;
if (nums[i]+nums[m]+nums[n] == 0) {
res.push_back({nums[i], nums[m], nums[n]});
}
if (nums[i]+nums[m]+nums[n] <= 0) {
int x = nums[m];
while(x == nums[++m] && m < n);
} else {
int x = nums[n];
while(x == nums[--n] && m < n);
}
}
}
return res;
}

总结

  1. 双指针,优化第二个数和第三个数遍历过程,将第三个数遍历过程变成一个从数组最右端开始向左移动的指针;
  2. 排序时间复杂度为 $O(n\log n)$,双指针时间复杂度为 $O(n^2)$,总时间复杂度为 $O(n^2)$。

15. 三数之和
https://ccccx159.github.io/2024/04/17/leetcode/15.三数之和/
作者
Xu@n Ch3n
发布于
2024年4月17日
更新于
2024年4月18日
许可协议