📝 题目描述
题目链接:三数之和
给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。
注意: 答案中不可以包含重复的三元组。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| 示例 1:
输入: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:
输入:nums = [0,1,1] 输出:[] 解释:唯一可能的三元组和不为 0 。
示例 3:
输入:nums = [0,0,0] 输出:[[0,0,0]] 解释:唯一可能的三元组和为 0 。
|
提示:
3 <= nums.length <= 3000
-10^5 <= nums[i] <= 10^5
💡 解题思路
方法一:双指针✅️
因为题目要求我们数字不能重复,所以可以将数组中的元素从小到大进行排序,保证枚举的三元组 (a,b,c) 满足 a≤b≤c,保证了只有 (a,b,c) 这个顺序会被枚举到,同时排序也可以让相同的数字相邻,便于我们后续去重判重。
步骤如下:
- 先将数组排序。
- 然后,我们从0索引开始,固定一个数
nums[i]。
- 接下来我们就可以在剩下的区间中,使用左右指针
L(i + 1) 和 R(指向最后一个元素) 寻找另外两个数字。
- 如果
nums[i] + nums[L] + nums[R] < 0,说明和太小,L 往右移;
- 如果
nums[i] + nums[L] + nums[R] > 0,说明和太大,R 往左移;
- 如果
== 0,记录答案(不能break,因为可能还有其他答案),并同时移动 L 和 R,跳过重复值。
这里还有一个比较好的剪枝思路,就是我们固定第一个数nums[i]时,如果nums[i] > 0,那么我们可以直接退出循环。因为数组已经排序,而题目要求和为0,如果nums[i] > 0,那么后续的数字肯定也都大于0,也就不存在和为0的组合了。
方法二:类似两数之和
复用两数之和的思路,首先也是需要先排序,便于后面跳过重复值。
接下来则需要提前将数字存放在哈希表中,由于之前排了序,这时重复的元素只记录的最后的索引,其余的被覆盖了。
然后同样固定一个数nums[i],那么剩下的就是寻找和为0 - nums[i]的数了,变成了两数之和问题,但这里找到答案时,需要限制(i < j) && (j < k),防止出现重复。
🔧 代码实现
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
| class Solution { public: vector<vector<int>> threeSum(vector<int>& nums) { vector<vector<int>> ans; int n = nums.size(); sort(nums.begin(), nums.end()); for (int i = 0; i < n; i++) { if (nums[i] > 0) break; if (i > 0 && nums[i] == nums[i-1]) continue; int L = i + 1; int R = n - 1; while (L < R) { int sum = nums[i] + nums[L] + nums[R]; if (sum == 0) { ans.push_back({nums[i], nums[L], nums[R]}); while (L < R && nums[L] == nums[L+1]) L++; while (L < R && nums[R] == nums[R-1]) R--; L++; R--; } else if (sum < 0) { L++; } else { R--; } } } return ans; } };
|
2、类似两数之和
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
| class Solution { public: vector<vector<int>> threeSum(vector<int>& nums) { vector<vector<int>> res; unordered_map<int, int> hash_map; sort(nums.begin(), nums.end()); for(int i = 0; i < nums.size(); i++){ hash_map[nums[i]] = i; } for(int i = 0; i < nums.size(); i++){ if ((i > 0) && (nums[i] == nums[i-1])){ continue; } for(int j = i + 1; j < nums.size(); j++){ if ((j > i + 1) && (nums[j] == nums[j-1])){ continue; } if(hash_map.find(0 - nums[i] - nums[j]) != hash_map.end()){ int k = hash_map[0 - nums[i] - nums[j]]; if((i < j) && (j < k)){ res.push_back({nums[i], nums[j], 0 - nums[i] - nums[j]}); } } } } return res; } };
|
📊 复杂度分析
1、双指针
- 时间复杂度:双重循环 O(N2) 的时间
- 空间复杂度:只需要 O(1) 的空间
2、类似两数之和
- 时间复杂度:双重循环 O(N2) 的时间
- 空间复杂度:使用了额外的 O(N) 空间
🎯 总结
- 核心思想:关键是想到“排序+相邻两次枚举的元素不能相同”来避免重复。