题目地址:力扣
这道题和两数之和不一样的是,现在不能用一个哈希表搞定了,就算我们用哈希表的话,还需要处理重复数的情况,变得更加麻烦了。如果我们直接暴力解的话,复杂度是O(),根据题目给的范围来看必定会超时,因此我们需要采用别的方法。
思路:这道题的关键地方在于不能出现重复的三元组,如果能出现重复的倒是用暴力也能得到正确答案。不能重复,那么就不知道下一个重复的数会出现在哪。因此这道题需要先对数组进行排序。排序后,从左边开始找,固定一个数寻找剩下两个数就好办得多。我们可以采用for循环的方式,而找剩下两个数可以通过双指针的方式。由于当前数组是排序过后的,因此已经是有序的,所以右边的数一定比左边的大,因此我们固定的数必须要小于0,才有可能使得其和后面两个数加在一起等于0。此外,固定一个数之后,剩下两个数可以通过从两边往中间移动的方式来看是否能这三数的和等于0。因为左边的数是比较小的(负数),右边的是比较大的(正数),因此通过这种方法就可能遍历所有的可能。
注意:这道题一个比较麻烦的地方就是去重,比如数组[-2, -1, -1, 0, 3, 3],我们固定-2,当左边指针指到-1的时候可以和最右边的3匹配使总和为0,此时将答案记录下来,将左边指针和右边指针同时向中间移动,但是会发现又碰到了-1和3,这样就会出现重复的三元组,不符合题目的要求。因此我们需要判断当前的元素和上一个元素是否相等,如果相等就直接继续移动(因为一旦有满足记录的情况出现,一定是第一次碰到这个数就记录下来了)。因此这道题需要对固定的第一个数进行去重,左指针和右指针的数也需要去重。
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
// 首先对数组进行排序
sort(nums.begin(), nums.end());
// sz记录数组大小,res为返回的二维数组
int sz = nums.size();
vector<vector<int>> res;
// 用于第一个数去重,采用pre记录上一个数值,初始化为第一个位置值减1
int prev = nums[0]-1;
// 第一个数进行遍历,遍历到倒数第三个数为止
for (int i = 0; i < sz-2; ++i)
{
// 如果当前数和上一个数的值相等就跳过本轮循环
if (nums[i] == prev)
continue;
// 若当前遍历的数已经大于零,后面不可能会出现等于0的情况,跳出循环
if (nums[i] > 0)
break;
// 左指针和右指针分别指向当前数的右边第一个数以及数组中最后一个数
int left = i+1, right = sz-1;
// 当左指针没碰到右指针的时候循环继续
while (left < right)
{
// 若三数之和大于0就把右指针往左移,减小总和
if (nums[i] + nums[left] + nums[right] > 0)
--right;
// 若三数之和小于0就把左指针向右移,增大总和
else if (nums[i] + nums[left] + nums[right] < 0)
++left;
// 若三数之和等于0
else
{
// 首先判断左指针和右指针是不是和上一次的结果相同
// 是的话就自增并跳出,因为当前结果已经被记录了
if (left > i+1 && nums[left] == nums[left-1])
{
++left;
continue;
}
if (right < sz-1 && nums[right] == nums[right+1])
{
--right;
continue;
}
// 第一个碰到,那么添加进结果数组并且左指针往右,右指针往左移
res.push_back(vector<int>{nums[i], nums[left++], nums[right--]});
}
}
// prev记录当前数的值
prev = nums[i];
}
// 循环结束返回结果数组
return res;
}
};
Accepted
- 311/311 cases passed (68 ms)
- Your runtime beats 83.48 % of cpp submissions
- Your memory usage beats 84.98 % of cpp submissions (19.3 MB)