题目:
Given an array S of n integers, are there elements a, b, c in S such that a + b + c = 0? Find all unique triplets in the array which gives the sum of zero.
Note:
- Elements in a triplet (a,b,c) must be in non-descending order. (ie, a ≤ b ≤ c)
- The solution set must not contain duplicate triplets.
For example, given array S = {-1 0 1 2 -1 -4}, A solution set is: (-1, 0, 1) (-1, -1, 2)
题目大意:
给定一个整数数组S,找出数组中的所有a,b,c使得a + b + c = 0,将所有的数字由大到小排序。
思路:
此题做的真是一波三折。拿到这道题的第一思路是枚举=、=。顿时感觉自己好Low啊。。。
经过观察题意可以发现涉及到排序了。。。那好吧,先排个序=、=,排完序后对边界条件进行了判断,主要有三个边界条件:nums为空,nums最大值小于0,nums最小值大于0。接下来就是算法的主要部分了:
1、从前向后选择两个较小值,下标分别是i,j从后向前选择一个较大值下标是k。
2、如果nums[i] + nums[j] + nums[k] < 0,则++j;
3、如果nums[i] + nums[j] + nums[k] > 0,则--k;
4、循环直到j 和k相等结束。
5、对结果进行去重。
(PS:因为用到了很多STL的东西,比如sort和unique,所以感觉会超时=、=)
代码:
class Solution {
public:
std::vector<std::vector<int>> threeSum(std::vector<int>& nums) {
std::vector<std::vector<int>> result;
//边界判断
if (nums.size() == 0) {
return result;
}
std::sort(nums.begin(), nums.end()); //排序
if (nums[0] > 0) {
return result;
} else if (nums[nums.size() - 1] < 0) {
return result;
}
for (int i = 0; i < nums.size(); ++i) { //选择第一个数
int j = i + 1; //第二个数的起始
int k = nums.size() - 1; //大数字的起始
while (j < k){ //循环控制
int tmp = nums[i] + nums[j] + nums[k];
if (tmp > 0) { //如果大于零,则--k
--k;
} else if (tmp < 0) { //如果小于0,则++j
++j;
} else {
std::vector<int> t = {nums[i], nums[j], nums[k]}; //加入结果数组
result.push_back(t);
--k; ++j;
}
}
}
//利用STL进行去重操作
std::sort(result.begin(), result.end());
auto end = std::unique(result.begin(), result.end());
result.erase(end, result.end());
return result;
}
};
果不其然。。。轰轰烈烈的超时了=、=
优化思想:
由上述思想可以看到是在将重复加入的结果后期通过调用STL来进行去重的,这个就显得有些冗余了,分析一下不难得出重复的地方无非就在取i、j、k进行循环时没有考虑当次的扫描值和上一次扫描值是否重复才造成的结果重复(因为已经排序了),所以只需要控制好选择时的条件(跳过重复项)即可。
优化后代码:
class Solution {
public:
std::vector<std::vector<int> > threeSum(std::vector<int> &num)
{
sort(num.begin(), num.end());
int i=0, j=0, k=num.size()-1;
int num1 = 0;
std::vector<int> tmp(3);
std::vector<std::vector<int> > result;
for (; i < k-1; ) {
num1 = 0-num[i];
tmp[0] = num[i];
for (j = i+1, k=num.size()-1; j < k; ) {
if (num[j] + num[k] == num1) {
tmp[1] = num[j]; ++j;
tmp[2] = num[k]; --k;
result.push_back(tmp);
while (j<k && num[j] == num[j-1]) { //如果当前选择扫描的第二个数字和其他的前一个已经处理过的数字相同则直接跳过
j++;
}
while (j<k && num[k] == num[k+1]) { //如果当前选择扫描的大数字和其他的前一个已经处理过的大数字相同则直接跳过
k--;
}
}
else if (num[j] + num[k] < num1) { //如果小于结果值说明小数字小了,把第二个选择的小数字向前后移动(即增大)
j++;
}
else { //如果大于结果值说明前边选择的两个小数字小了,所以把选择的大数字向前移动(即减小)
k--;
}
}
i++;
while (i<k-1 && num[i] == num[i-1]) { //对i值判重,如果和之前扫描的重复,则跳过
i++;
}
}
return result;
}
};