给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请
你返回所有和为 0 且不重复的三元组。
注意: 答案中不可以包含重复的三元组。
示例 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-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,说明我们的元素选大了,就要调小一点,所以right向左移动也就是right--(这就是为什么要先排序的原因,数组才能向右递增!) - 如果
nums[i] + nums[left] + nums[right] < 0,说明我们的元素选小了,就要调大一点,所以left向右移动也就是left++ - 如果
nums[i] + nums[left] + nums[right] == 0,说明符合要求,这个时候就 将三个指针的值分别push_back到数组中去。- 注意有个关键的点,就是尾插完因为我们还得继续移动
left和right直到它们两个相遇,那么就有可能会出现重复的元素组合,那么这个时候就要对left和right处的元素去重,利用while循环判断一下:- 如果说此时
nums[left] == nums[left + 1]的话,说明后面的组合也是会重复的,直接left++。对于right也是如此!
- 如果说此时
- 除此之外就是
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的话,那么就错了,因为这三个元素是能构成一个组合的!
- 可能有人就会问为什么不是判断
- 注意有个关键的点,就是尾插完因为我们还得继续移动
- 此时如果
num[i]大于0,那么因为排序的原因,后面的元素肯定不会出现负数和0,也就是说不会再出现和为0的组合了,那么直接break即可!
除此之外,还有一个细节,就是在双指针 left 和 right 移动的时候,判断结束条件是 while(left < right) 还是 while(left <= right) 呢❓❓❓
其实正确的是 while(left < right),思路很简单,就是如果 left = right ,我们本来就是要三元组的,那么相等了之后,不就两个数重合了吗,那就变成二元组了,这就不符合题意了,所以正确的是 while(left < right) !
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;
}
};