15. 三数之和
给定一个包含 n 个整数的数组 S,是否存在属于 S 的三个元素 a,b,c 使得 a + b + c = 0 ?找出所有不重复的三个元素组合使三个数的和为零。
注意:结果不能包括重复的三个数的组合。
例如, 给定数组 S = [-1, 0, 1, 2, -1, -4],
一个结果集合为:
[
[-1, 0, 1],
[-1, -1, 2]
]
ps:这题可以转换target的值,从而将题目变为两数之和的做法(两数之和题目戳我阅读)。下面是我一开始的做法:
步骤1:先判断nums数组的大小是否小于3。
步骤2:对nums进行稳定排序(我这种做法和他们的下标有联系)
步骤3:将排序后的数组保存到num结构体数组中。
步骤4:找出nums数组里所有数的相反数,用set去重,同样保存他们的下标(目的是最后答案数组的去重)。
步骤5:有了相反数后,遍历所有的相反数,令target = opposize[i],这样在结构体数组中用双指针找出两数之和等于target,注意两个数的下标不能等于target的下标。
步骤6:将符合的数组保存,这里我用map去掉重复的数组。
static const auto __ = []()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
return nullptr;
}();
struct OPPOSIZE
{//相反数结构体数组
int value;
int index;
OPPOSIZE(int value, int index): value(value), index(index) {}
friend bool operator < (const OPPOSIZE& p1, const OPPOSIZE& p2)
{
return p1.value < p2.value;
}
};
struct NUM
{
int value;
int index;
};
bool cmp(const NUM& n1, const NUM& n2)
{
return n1.value < n2.value;
}
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
//1
vector<vector<int> >ans;
if(nums.size() < 3)return ans;
//2
int nums_size = nums.size();
stable_sort(nums.begin(), nums.end());
//3
vector<NUM> num;
NUM temp1;
for (int i = 0; i < nums_size; i++)
{
temp1.value = nums[i];
temp1.index = i;
num.push_back(temp1);
}
//4保存相反数
set<OPPOSIZE> opposize;
set<int> _opp;
int opp_size = _opp.size();
for (int i = 0; i < nums_size; ++i)
{
_opp.insert(-nums[i]);
if (_opp.size() > opp_size)
{
OPPOSIZE temp(-nums[i], i);
opposize.insert(temp);
opp_size = _opp.size();
}
}
map<vector<int>, int> m_ans;
//5
for (set<OPPOSIZE>::iterator it = opposize.begin(); it != opposize.end(); ++it)
{
int target = (*it).value;
int t_index = (*it).index;
int top = 0;
int tail = nums_size - 1;
while (top < tail)
{
if (top == t_index)
{
++top;
continue;
}
else if (tail == t_index)
{
--tail;
continue;
}
if (num[top].value + num[tail].value == target)
{
vector<int>t_ans;
if (t_index < num[top].index && t_index < num[tail].index)
{
t_ans.push_back(-(*it).value);
if (num[top].index < num[tail].index)
{
t_ans.push_back((num[top].value));
t_ans.push_back((num[tail].value));
}
else
{
t_ans.push_back((num[tail].value));
t_ans.push_back((num[top].value));
}
}
else if (num[top].index < t_index && num[top].index < num[tail].index)
{
t_ans.push_back(num[top].value);
if (t_index < num[tail].index)
{
t_ans.push_back(-(*it).value);
t_ans.push_back(num[tail].value);
}
else
{
t_ans.push_back(num[tail].value);
t_ans.push_back(-(*it).value);
}
}
else
{
t_ans.push_back(num[tail].value);
if (t_index < num[top].index)
{
t_ans.push_back(-(*it).value);
t_ans.push_back(num[top].value);
}
else
{
t_ans.push_back(num[top].value);
t_ans.push_back(-(*it).value);
}
}
//6
m_ans.insert(map<vector<int>, int>::value_type(t_ans, 1));
++top;
--tail;
}
else if (num[top].value + num[tail].value > target)
{
--tail;
}
else
{
++top;
}
}
}
for (map<vector<int>, int >::iterator it = m_ans.begin(); it != m_ans.end(); it++)
{
ans.push_back(it -> first);
}
return ans;
}
};
后来上交过了,我去看了一下别人的代码(好简洁啊!),原来可以进一步优化,做法大概是:
1.先对原数组进行排序
2.遍历该数组,先确定相反数(他们的去重很巧妙)。如果该数大于0,则循环结束(这与去重做法有关),如果这个数与前面的数相同,则跳过。
3.下来是精髓部分,我们找到一个相反数后去哪找另外的两个数呢。查找范围就是
(相反数的下标+1, nums.size() - 1)。
为什么是这样呢? 3数之和的目标是target(这道题是0)。首先如果选出的数是第一数,那就不用解释。如果选出的数不是第一个数呢?假设这个数是nums[i](0 < i < nums.size() - 2),如果我们要查找的范围是(0,i-1)和(i+1,nums.size()-1),假如我们要的数有一个数k在(0,i-1)中,那么另外的一个数假设是m。即存在
k + m = target - nums[i], m + nums[i] = target - k(注意这里之前我们已经求过target - k),
因此为了去重,可以把(0, i - 1)的范围去掉,接下来目标就是位于
(i + 1, nums.size() - 1)
的两数之和问题了。
class Solution
{
public:
vector<vector<int>> threeSum(vector<int>& nums)
{
vector<vector<int> >ans;
if (nums.size() < 3) return ans;
sort(nums.begin(), nums.end());
int new_size = nums.size() - 2;
for (int i = 0; i < new_size; ++i)
{
if (nums[i] > 0) break;
if (nums[i] == nums[i - 1] && i) continue;//去重
int target = -nums[i];
int top = i + 1, tail = new_size + 1;
while (top < tail)
{
if (nums[top] + nums[tail] == target)
{
ans.push_back({nums[i], nums[top], nums[tail]});
while ((top + 1) < tail && nums[top] == nums[top + 1])++top;//去重
while ((tail - 1) > i && nums[tail] == nums[tail - 1])--tail;//去重
++top;
--tail;
}
else if (nums[top] + nums[tail] > target) --tail;
else top++;
}
}
return ans;
}
};