15. 三数之和
难度中等5626
给你一个整数数组 nums
,判断是否存在三元组 [nums[i], nums[j], nums[k]]
满足 i != j
、i != k
且 j != k
,同时还满足 nums[i] nums[j] nums[k] == 0
。请
你返回所有和为 0
且不重复的三元组。
注意: 答案中不可以包含重复的三元组。
示例 1:
代码语言:javascript复制输入: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:
代码语言:javascript复制输入:nums = [0,1,1]
输出:[]
解释:唯一可能的三元组和不为 0 。
示例 3:
代码语言:javascript复制输入:nums = [0,0,0]
输出:[[0,0,0]]
解释:唯一可能的三元组和为 0 。
提示:
3 <= nums.length <= 3000
-105 <= nums[i] <= 105
解题思路:排序 双指针
因为这道题要求答案中不能有重复的三元组,那么我们肯定要进行去重的操作,也是这道题的关键!
如果这道题我们用的是哈希,那么会比较复杂一点,就像这道 454. 四数相加 II 一样,我们可以将两个数组的各位和先加载到 hash 中,然后遍历第三个数组,判断是否和为 0,当然要做去重操作,相对应会比较复杂,难控制,所以这里我们不采用哈希的方法!
这里我们采用排序 双指针来解决,这样子去重的步骤也比较简单!
首先我们为什么要先排序呢 ???因为我们的目的是为了得到和为 0 的三个数,并且使用的是双指针,两个指针在左右两边开始遍历,这样子的话我们其实排序完就会有天然的 “平分” 情况,因为除了为全 0 的三个数,要想得到和为 0,那么肯定会有负数的存在,所以我们相当于左右指针被 0 作为中介隔开,这是为了我们使用双指针的时候,通过中介 0 来判断挪动哪个指针的因素!并且这样子 对我们后面判断是否重复是很关键!
接下来就是双指针移动的过程:用循环遍历每个元素,用 i 表示当前下标,left 为 i 1,right 为数组末尾也就是 nums.size() - 1。然后每次移动 left 和 right ,直到它们相遇!
- 如果
nums[i] nums[left] nums[right] > 0
,总和大于0,说明我们的元素选大了,就要调小一点,所以 right 向左移动也就是right--
(这就是为什么要先排序的原因,数组才能向右递增!) - 如果
nums[i] nums[left] nums[right] < 0
,总和小于0,说明我们的元素选小了,就要调大一点,所以 left 向右移动也就是left
- 如果
nums[i] nums[left] nums[right] == 0
,总和等于0,说明符合要求,这个时候就将三个指针的值分别 push_back 到数组中去
。- 注意有个关键的点,就是尾插完因为我们还得继续移动 left 和 right 直到它们两个相遇,那么就有可能会出现重复的元素组合,那么这个时候就要对
left 和 right 处的元素去重
,利用 while 循环判断一下!具体看代码! - 除此之外就是
i 也要去重
,我们可以知道,我们每次遍历一个 i 的时候,其实通过后面的双指针 left 和 right 已经将 i 指针的值所有可能已经走过一遍了,那么此时去重 i 指针的值就很简单,只需要判断前一个 num[i - 1] 是否与当前的 nums[i] 相同,是的话说明重复了,则直接 continue,直到遇到不重复的 i 为止!
(注意要判断 nums[i-1] 的时候需要先判断 i > 0,不然会越界!)- 可能有人就会问为什么不是判断 nums[i] 与 nums[i 1] 是否相同呢,因为我们的 left 固定是一个组合的数,那么可能就会出错,比如说 {-1, -1, 2},此时 i、left、right 分别指向三个元素,如果判断了 nums[i] 等于 nums[i 1] 然后直接 continue的话,那么就错了,因为这三个元素是能构成一个组合的!
- 注意有个关键的点,就是尾插完因为我们还得继续移动 left 和 right 直到它们两个相遇,那么就有可能会出现重复的元素组合,那么这个时候就要对
此时如果 num[i] 大于 0,那么因为排序的原因,后面的元素肯定不会出现负数和 0,也就是说不会再出现和为 0 的组合了,那么直接 break 即可
!
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
sort(nums.begin(), nums.end()); // 关键性的一步排序别忘了
vector<vector<int>> vv;
int n = nums.size();
for(int i = 0; i < n; i)
{
// 排序之后如果第一个元素已经大于零,那么无论如何组合都不可能凑成三元组,直接返回结果就可以了
if(nums[i] > 0)
break;
// 去重i的方法
if(i > 0 && nums[i] == nums[i - 1])
continue;
int left = i 1, right = n - 1;
while(left < right)
{
if(nums[i] nums[left] nums[right] > 0)
right--;
else if(nums[i] nums[left] nums[right] < 0)
left ;
else
{
vv.push_back(vector<int>{nums[i], nums[left], nums[right]});
// 对left和right进行去重
while(left < right && nums[right] == nums[right-1])
right--;
while(left < right && nums[left] == nums[left 1])
left ;
// 注意此时还要将left和right更新一次才是不重复元素
right--;
left ;
}
}
}
return vv;
}
};