分析问题
这道题目非常的奇怪 出现的位置更是奇怪
这道题目在代码随想录的哈希章节看到的,但是这道题目基本不会用哈希方法去解决
就当是复习快慢指针了吧
题解
给你一个数组,从其中任意取出3个数(不可重复取出) 这三个数如果相加==0,则满足要求加入到二维数组中去。但是很重要的一点就是,这三个数取出后a,b,c 如果我后面又找到了一样的a,b,c 此时我们要去重 。此题一个是想到如何去做非常的困难,而且去重部分的逻辑也是很精妙,稍有不慎就是错误.
小结
题目的意思很明确,取出三个数字相加==0,则放入数组,但是这三个数的组合已经不能再次出现,否则重复了,需要去重。
重点
规律,如果只要数字,不要下标,而且不能重复。那么我们可以尝试先排序。
这样可以方便我们去重.
-暴力
基本代码
暴力算法三个循环嵌套。那么我们便可以取出所有的组合
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> ans;
int size = nums.size();
for(int a = 0;a<size-2;a++){
int n1=nums[a];
//b循环
for(int b = a+1; b<size-1;b++){
int n2=nums[b];
//c循环
for(int c = b+1;c<size;c++){
int n3=nums[c];
if(n1+n2+n3==0){
ans.push_back(vector<int>{n1,n2,n3});
break;
}
}
//c循环
}
//b循环
}
return ans;
}
这是第一部分,我们完成了基本的逻辑 此时输出 我们发现有重复的部分
接下来就是关键的去重操作
去重
我们想到去重的话,记录元素有没有出现过,那么最好的是unordered_set,这道题目理论上也是可以的,但是我不知道怎么去搞,有点混乱复杂,所以我们需要一点技巧。
在讲解代码前,有一个题目需要讲一下。两数之和,leetcode上面有一套类似的,不过他要求出下标,而我只需要数值便可以,其他要求类似,不重复。
例如:
我给出{-1,3,2,2,1,-1,-1,1,-2}我要求给出所有两个数相加==0的组合,且不能重复。
讲解
这道题目很显然双指针,一个slow放在开头,fast则是++ ,如果双指针指向的数字相加==0符合就退出。
但是如何辨别slow已经指向过-1呢。
我们说到
但是这样会有多余的hash计算和空间的占用,即使非常的渺小。
排序
我发现过一个规律,如果只要数字,不要下标,而且不能重复。那么我们可以尝试先排序。
如果slow的指针下标不为0,而且nums[slow--]==nums[slow]。那么我们完全可以跳过这个数字,因为第一次我们已经把所有符合条件的全部遍历过了
vector<vector<int>> threeSum(vector<int>& nums) {
sort(nums.begin(),nums.end());
for(int i:nums)cout<<i<<" ";
cout<<endl;
vector<vector<int>> ans;
int size = nums.size();
for(int a = 0;a<size-2;a++){
int n1=nums[a];
if(a!=0&&n1==nums[a-1])continue;
//b循环
for(int b = a+1; b<size-1;b++){
int n2=nums[b];
if(b!=a+1&&n2==nums[b-1])continue;
//c循环
// for(int c = b+1;c<size;c++){
// int n3=nums[c];
// if(c!=0&&n3==nums[c-1])continue;
// if(n1+n2+n3==0){
// ans.push_back(vector<int>{n1,n2,n3});
// break;
// }
// }
//c循环
if(n1+n2==0){
ans.push_back(vector<int>{n1,n2});
break;
}
}
//b循环
}
return ans;
}
这样就可以非常的帅气去除重复的答案,如果你理解了 那么 c循环里面应该干什么你就知道怎么写了。
vector<vector<int>> threeSum(vector<int>& nums) {
sort(nums.begin(),nums.end());
for(int i:nums)cout<<i<<" ";
cout<<endl;
vector<vector<int>> ans;
int size = nums.size();
for(int a = 0;a<size-2;a++){
int n1=nums[a];
if(a!=0&&n1==nums[a-1])continue;
//b循环
for(int b = a+1; b<size-1;b++){
int n2=nums[b];
if(b!=a+1&&n2==nums[b-1])continue;
//c循环
for(int c = b+1;c<size;c++){
int n3=nums[c];
if(c!=b+1&&n3==nums[c-1])continue;
if(n1+n2+n3==0){
ans.push_back(vector<int>{n1,n2,n3});
break;
}
}
//c循环
}
//b循环
}
return ans;
}
请注意 嵌套中的b,c判断不再是0,而是上一层的+1.
-双指针(三指针)
分析
看到上面这种解法,也许你会有所启发。
但是苦于这题三数,难道有新的方法叫做三指针。
其实没有,但是这题可以拆解为一个指针和双指针的问题。
讲解
第一个循环a是要留下的,毕竟有1才有2.我们需要知道从哪里开始。
我们主要优化的是2,3循环。
怎么优化呢?如果我说给你数字a,现在请你遍历数组给出b,c。那么问题变成了什么?
双指针。我们在nums[a]~nums.back()中 需要得出==-a的结果。
动手
我们保留a循环,剔除b,c循环;
在数组中快速的求出和为-a的数字组合。
如果你做过这道题目,那么你就会知道。
我们可以定义int left = a+1,right = nums.size()-1;
如果他们相加>a那么right--,反left++.这里有二分法的思想.
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
sort(nums.begin(),nums.end());
vector<vector<int>> ans;
int size = nums.size();
for(int a = 0;a<size;a++){
int n1=nums[a];
if (nums[a] > 0) {
return ans;
}
if(a!=0&&n1==nums[a-1])continue;
int left = a+1;
int right = size-1;
while(left<right){
int sum = nums[left]+nums[right]+n1;
if(sum>0)right--;
else if(sum<0)left++;
else if(sum==0){
ans.push_back(vector<int>{n1,nums[left],nums[right]});
while(left<right&&nums[left]==nums[left+1])left++;
while(left<right&&nums[right]==nums[right-1])right--;
left++;
right--;
}
}
}
return ans;
}
};