这题其实不容易调通,难就难在对重复元素的排除上。比如说a[]={-2,0,0,2,2},只能输出一个[-2,0,2],但对于a[]={-4,-1,-1,0,1,2,3},必须要输出2个: [-1,-1,2]和[-1,0,1],而对于a[]={0,0,0,0},又只能输出一个[0,0,0]。
注意:
1) 此题不存在O(nlogn)的解法。我一开始设想了一个解法是用两个指针p1,p2,一前一后。假设两个指针对应数和为sum,中间那个数用binary search找-sum。如果找不到,并且sum>0,就p2--,否则p1++。
比如说,a[]={-7,-4,0,1,1,3},-7+3=-4找4找不到,p1++,-4+3=-1,找到了。
但这种方法是不对的。举个反例,a[]={-7,-4,-4,0,1,1,2,8},-7+8=1,此时要p2--,但p2不能动,因为-4-4+8=0。这个binary search的方法不对的原因我认为是:如果找不到解,不能确定p1和p2怎么动,有可能只p1++,也可能只p2--,也可能p1++和p2--都要进行,所以这种试图两边往中间移动的思路不对。
2) 正解的方法是只锁定一边,也就是i++,然后从i+1到nums.size()-1里面用p1,p2,先算sum2=nums[p1]+nums[p2],然后sum2和-nums[i]比较,若sum2>-nums[i],则p2--; 若sum2<-nums[i],则p1++;如果两者相等,说明找到一个解。要注意此时应该再加一个while循环,把所有的解都弄出来。
这两行非常重要,里面又是两个小循环:
while ((++p1<p2) && (nums[p1]==nums[p1-1]));
while ((p1<--p2) && (nums[p2]==nums[p2+1]));
它们表示将p1,p2移到不重复的地方,假设nums[1]=-1, p1~p2对应[-1,-1,0,1,2],此时找到一个解[-1,-1,2]。第一个while会将数组变成[-1,0,1,2],第二个while会将数组变成[-1,0,1],然后while(p1<p2)循环又会发现另一个解[-1,0,1]。注意这两个解都是i=1时发现的。
但如果input数组是[-2,0,0,2,2,2,2]时,一开始while(p1<p2)的循环内会发现[-2,0,2]这个解,过完上面两个循环数组会变成[0,0,2],所以不会有重复解出现。
我一开始将上面两个循环写成
while ((p1<p2) && (nums[p1]==nums[p1-1])) p1++;
while ((p1<p2) && (nums[p2]==nums[p2+1])) p2--;
3) 最开始的 if ((i==0)||(nums[i]!=nums[i-1]))也可以防止重复解出现。如果nums[i-1]和nums[i]都是解的一部分也没关系,上次i-1的时候就已经输出了(因为已经i++),这是可以直接跳过nums[i]。
4) 此题避免重复解也可以用set或map。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
vector<vector<int> > threeSum(vector<int>& nums) {
int i=0, p1=0, p2=0;
vector<vector<int> > sol;
if (nums.size()==0) return sol;
sort(nums.begin(), nums.end());
while(i<nums.size()-1) {
if ((i==0)||(nums[i]!=nums[i-1])) {
p1=i+1; p2=nums.size()-1;
while(p1<p2) { //target is -nums[i]
int sum2=nums[p1]+nums[p2];
if (sum2>-nums[i])
p2--;
else if (sum2<-nums[i])
p1++;
else {
//find one solution
vector<int> tmpV;
tmpV.push_back(nums[p1]); tmpV.push_back(nums[p2]); tmpV.push_back(nums[i]);
sol.push_back(tmpV);
cout<<" ["<<nums[p1]<<", "<<nums[p2]<<", "<<-sum2<<"]"<<endl;
while ((++p1<p2) && (nums[p1]==nums[p1-1])); //Attention, ++p1
while ((p1<--p2) && (nums[p2]==nums[p2+1])); //Attention, --p2
}
}
}
i++;
}
return sol;
}
int main()
{
vector<int> S1={-1, 0, 1, 2, -1, -4};
threeSum(S1);
cout<<endl;
vector<int> S2={0,0,0,0};
threeSum(S2);
cout<<endl;
vector<int> S3={-2,0,1,1,2};
threeSum(S3);
cout<<endl;
vector<int> S4={-2,0,0,2,2};
threeSum(S4);
cout<<endl;
vector<int> S5={-2,0,1,1,1,2};
threeSum(S5);
cout<<endl;
vector<int> S6={-3,1,2,2,2,2};
threeSum(S6);
cout<<endl;
vector<int> S7;
threeSum(S7);
cout<<endl;
return 0;
}
又做了一遍,第二次的代码如下,其实跟第一次差不多。
vector<vector<int>> threeSum(vector<int> &numbers) {
vector<vector<int>> result;
if (numbers.size()<3) return result;
int p1=0, p2=0, p3=0;
sort(numbers.begin(), numbers.end());
for (p3=2; p3<numbers.size(); ++p3) {
if ((p3<numbers.size()-1) && (numbers[p3]==numbers[p3+1])) continue;
p1=0; p2=p3-1;
while(p1<p2) {
int sum=numbers[p1]+numbers[p2];
if (sum+numbers[p3]==0) {
vector<int> temp={numbers[p1],numbers[p2],numbers[p3]};
result.push_back(temp);
while(p1<p2 && (numbers[p1++]==numbers[p1]));
while(p1<p2 && (numbers[p2--]==numbers[p2]));
} else if (sum+numbers[p3]<0) {
p1++;
} else {
p2--;
}
}
}
return result;
}
3刷,发现自己以前水平太次了。
class Solution {
public:
/**
* @param numbers: Give an array numbers of n integer
* @return: Find all unique triplets in the array which gives the sum of zero.
*/
vector<vector<int>> threeSum(vector<int> &numbers) {
int n = numbers.size();
if (n < 3) return {{}};
sort(numbers.begin(), numbers.end());
int p1 = 0, p2 = 0, p3 = 2;
set<vector<int>> s;
vector<vector<int>> result;
while(p3 < n) {
p1 = 0; p2 = p3 - 1;
int target = -numbers[p3];
while(p1 < p2) {
int sum = numbers[p1] + numbers[p2];
if (sum == target) {
s.insert({numbers[p1], numbers[p2], numbers[p3]});
p1++; p2--;
} else if (sum > target) {
p2--;
} else {
p1++;
}
}
p3++;
}
result.assign(s.begin(), s.end());
return result;
}
};
4刷
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> result;
int len = nums.size();
if (len < 3) return result;
sort(nums.begin(), nums.end());
for (int i = 2; i < len; i++) {
int start = 0, end = i - 1, target = -nums[i];
if (i < len - 1 && nums[i] == nums[i + 1]) continue;
while (start < end) {
int sum = nums[start] + nums[end];
if (sum > target) {
end--;
} else if (sum < target) {
start++;
} else {
result.push_back({nums[start], nums[end], nums[i]});
start++; end--;
while (start < end && nums[start] == nums[start - 1]) start++; //注意这里是start - 1
while (start < end && nums[end] == nums[end + 1]) end--; //注意这里是end + 1
}
}
}
return result;
}
};
代码同步在
GitHub - luqian2017/Algorithm: LintCode, LeetCode, Uva, etc