1.题目描述:
Given an array nums of n integers, are there elements a, b, c in nums such that a + b + c = 0? Find all unique triplets in the array which gives the sum of zero.
Note:
The solution set must not contain duplicate triplets.
Example:
Given array nums = [-1, 0, 1, 2, -1, -4],
A solution set is:
[
[-1, 0, 1],
[-1, -1, 2]
]
来自 <https://leetcode.com/problems/3sum/>
2.问题分析:
经典的3sum问题,在比较多的面经中都会讲到,可见这道题和变种题在面试中出现的概率还是相当大的。
从数组中找出三个数,使得和为0;暴力的方法就是一个一个试验,算法复杂度O(n3),当输入是超长数组时,严重超时。同时有一个需要处理的是,不能出现重复的答案,暴力的方法不进行这种处理,最后的结果中会出现比较多的重复项,必须对结果进行二次处理(处理的复杂度O(n2)),才能返回。
3.c++代码:
//Time Limit Exceeded
vector<vector<int>> threeSum(vector<int>& nums)
{
vector<vector<int>> rr;
if (nums.empty())
return rr;
vector<int>v(nums);
sort(v.begin(), v.end());
int n = v.size();
for (int mid = 1; mid < n - 1; mid++)
{
for (int left = 0; left < mid; left++)
{
for (int right = mid + 1; right < n; right++)
{
if (v[left] + v[right] + v[mid] == 0)
{
vector<int>tmp;
tmp.push_back(v[left]);
tmp.push_back(v[mid]);
tmp.push_back(v[right]);
rr.push_back(tmp);
}
}
}
}
if (rr.size() > 1)
{
vector<vector<int>> rrr;
rrr.push_back(rr[0]);
for (vector<vector<int>>::iterator i = rr.begin(); i != rr.end(); i++)
{
int cnt = 0;
for (vector<vector<int>>::iterator j = rrr.begin(); j != rrr.end(); j++)
{
if (*i == *j)
cnt++;
}
if (cnt == 0)
rrr.push_back(*i);
}
return rrr;
}
else
return rr;
}
关于下面这个方法的分析:
- 先对数组进行排序O(NlogN)
- 定义左右两个指针,中间数从左往右遍历,左指针在左区间活动,右指针在右区间活动
根据求和结果移动指针,如果和<0,说明,左边的数偏小,更新向中间数方向更新左指针,如果和>0,说明,右边的数偏小,向中间数方向更新右指针,知道找到合适的答案; - 怎么去除重复答案的?
首先,重复答案有两种,一种是左右指针的数重复,另一种是中间数重复;
对于第一种的解决方法是,在找到合适答案时,移动左右指针,直到左右数不重复;
第二种的解决方法是,保存上次的合适答案的指针位置,如果当前中间数和上一个中间数一样,则从上次指针的位置的开始查找,这样就跳过了重复的中间数对象的重复左右指针数了,这个办法通过缩短左右区间,跳过出现重复左右数的区域。
//beats 43.52%
vector<vector<int>> threeSum2(vector<int>& nums)
{
vector<vector<int>>res;
if (nums.empty())
return res;
sort(nums.begin(), nums.end());
int n = nums.size();
int tmp_l = 0;int tmp_r = n - 1;
int left = 0;int right = n - 1;
for(unsigned int mid=1;mid < n - 1;mid++)
{
if (nums[mid] == nums[mid - 1])
{left = tmp_l; right = tmp_r;}
else
{ left = 0;right = n - 1;}
while (left<mid&&right>mid)
{
int s = nums[mid] + nums[left] + nums[right];
if (s < 0)left++;
else if (s > 0)right--;
else
{
res.push_back(vector<int>{ nums[left], nums[mid], nums[right]});
while (nums[left] == nums[left + 1])left++;
while (nums[right] == nums[right - 1])right--;
left++;
right--;
tmp_l = left;
tmp_r = right;
}
}
}
return res;
}
4.图表分析:
输入:[-1,0,1,2,-1,-4,2,-1]
排序后:[-4,-1,-1,0,1,2,2,-1]
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|
-4 | -1 | -1 | -1 | 0 | 1 | 2 | 2 |
1.mid=1
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | sum |
---|---|---|---|---|---|---|---|---|
-4 | -1 | -1 | -1 | 0 | 1 | 2 | 2 | |
left | mid | right | -3 | |||||
tmp_left | tmp_right |
sum=-4±1+2=-3<0,更新left,left==mid,停止
2.mid=2
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | sum |
---|---|---|---|---|---|---|---|---|
-4 | -1 | -1 | -1 | 0 | 1 | 2 | 2 | sum |
left | mid | right | -3 | |||||
left | mid | right | 0 | |||||
left | mid | right | 0 | |||||
tmp_left | tmp_right |
找到合适的答案:[-1,-1,2]
右指针区域的两个2都是满足条件的,更新right到最左边的2
tmp_left/tmp_right更新到下一个位置
3.mid =3
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | sum |
---|---|---|---|---|---|---|---|---|
-4 | -1 | -1 | -1 | 0 | 1 | 2 | 2 | |
left | mid | right | 1 | |||||
left | mid | right | 0 | |||||
tmp_left/tmp_right |
由于nums[3]=-1上次出现过了,本轮产找的区间不再是从数组的两个边界开始,而是从tmp_left和tmp_right开始。
4.mid=4
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | sum |
---|---|---|---|---|---|---|---|---|
-4 | -1 | -1 | -1 | 0 | 1 | 2 | 2 | |
left | mid | right | -2 | |||||
left | mid | right | 1 | |||||
left | mid | right | -1 | |||||
left | mid | right | 0 | |||||
tmp_left | tmp_right |
找到合适的答案:[-1,0,1]
5.mid=5
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | sum |
---|---|---|---|---|---|---|---|---|
-4 | -1 | -1 | -1 | 0 | 1 | 2 | 2 | |
left | mid | right | -1 | |||||
left | mid | right | 2 | |||||
left | mid | right | 2 |
5.mid=6
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | sum |
---|---|---|---|---|---|---|---|---|
-4 | -1 | -1 | -1 | 0 | 1 | 2 | 2 | |
left | mid | right | 0 | |||||
tmp_left | tmp_right |
找到合适的答案:[-4,2,2]