您的位置:首页 > 其它

leetcode 第15题:两数求和的扩展

2017-09-04 17:09 281 查看

题目大意:

  给定一个包含n个整数的数组,试问能否找到三个的元素,使得它们的和为零?返回所有可能的元素集合。

说明:

  返回的是一个三元不定长数组的集合,其中的元素不可重复

示例:

  输入vector S:[-1,0,1,2,-1,-4],应当输出[ [-1,0,1],[-1,-1,2] ]。注意,[-1,0,1]应当只输出一次,尽管给定的vector中包含两个-1。

解题思路:

  解题前,需要明确的一点是题目不一定有解,甚至给出的vector长度可能小于三,不可想当然。

  一开始解这道题时,我首先明确了暴力法的不可行,因其复杂度高达O(N^3)。之后,我试着换了一个思路:通常来说,三个数的和为0,意味着其中两个数大于等于0,一个数小于等于0,或者两个数小于等于0,一个数大于等于0,因此只要将其分成正负两个从小到大排列好的集合,一个集合逐次相加,另一个集合中搜寻对应元素即可。实现代码如下:

class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector< vector<int> > ans;
int ifmanyzeros=0,index=-1,he,k=0;
if(nums.size()<3)
re
4000
turn ans;
sort(nums.begin(),nums.end());  //对数组元素排序
for(int i=2;i<nums.size();i++)  //对数组“瘦身”,注意至少有三个元素
{
if(nums[i-1]==nums[i-2]&&nums[i]==nums[i-1])  //删除两个以上的重复元素
{
if(nums[i]==0)
{
ifmanyzeros=1;
}  //有多个零必须予以记录
nums.erase(nums.begin()+i);
i--;
}
}
if(ifmanyzeros)
{
vector<int> data;
data.push_back(0);
data.push_back(0);
data.push_back(0);
ans.push_back(data);
}
for(int i=0;i<nums.size()-1;i++)
{
if(index==-1&&nums[i]<=0&&nums[i+1]>=0)  //记录正负交界的位置
{
index=i;
}
if(nums[i]==0&&nums[i+1]==0)  //最多只剩一个0
nums.erase(nums.begin()+index+1);
}
for(int j=0;j<=index;j++)  //对数组进行二次遍历
{
if(j>=1&&nums[j]==nums[j-1])  //如果是重复元素则跳过
continue;
for(k=j+1;k<=index;k++)  //否则逐个求和,找相等元素
{
if(k>j+1&&nums[k]==nums[k-1])  //剪枝
continue;
he=(nums[j]+nums[k])*-1;
vector<int>::iterator res=find(nums.begin()+index+1,nums.end(),he);
if(res!=nums.end())  //如果找到该元素,则保留结果
{
vector<int> data;
data.push_back(nums[j]);
data.push_back(nums[k]);
data.push_back(*res);
ans.push_back(data);
}
}
}
for(int j=index+1;j<nums.size();j++)
{
if(j>=index+2&&nums[j]==nums[j-1])  //如果是重复元素则跳过
continue;
for(k=j+1;k<nums.size();k++)  //否则逐个求和
{
if(k>j+1&&nums[k]==nums[k-1])  //剪枝
continue;
he=(nums[j]+nums[k])*-1;
vector<int>::iterator res=find(nums.begin(),nums.begin()+index+1,he);
if(res!=(nums.begin()+index+1))
{
vector<int> data;
data.push_back(nums[j]);
data.push_back(nums[k]);
data.push_back(*res);
ans.push_back(data);
}
}
}
return ans;
}
};


  我做了预处理。先对数组排序,并对于重复出现三次及以上的元素只保留前两个,重复出现的0只保留一个,随后执行以上的算法。同时,若出现三个及以上的0,还必须再往最后的结果中加入[0,0,0].不难发现,这样写有些将问题复杂化了。而且和暴力法比起来,虽然相对减小了问题的规模,并做了几处剪枝,但复杂度依然是O(N^3)级别,对于大规模的问题仍然会出现TLE的错误。

  参考了Discuss中的结果后,我才终于明白,这其实是对“一个数组中寻找两数,其和为0”问题的一次扩展。解决这个问题的思路在于:将数组从小到大排列,用标志位记录数组一前一后两个元素,将其求和,结果与0比较,大于0,则说明后标志位有下降的空间,因此将后标志位减一;小于0,则说明前标志位有上升的空间,将前标志位加一;等于0,则记录结果并保存。如此,直到前后标志位重合,则结束循环。这样做,只需遍历一遍数组就可以得到最终结果。问题扩大到三个数,基本思想是一致的,即先确定第一个数,再通过上述方法寻找另外两个数。实现代码如下:

class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector< vector<int> > ans;
sort(nums.begin(),nums.end());  //先对数组进行排序
for(int i=0;i<nums.size();i++)
{
if(i==0||nums[i]!=nums[i-1])  //剪枝
{
int rl=i+1,rr=nums.size()-1;  //记录中间两数的下标
while(rl<nums.size()-1&&rr>1&&rl<rr)
{
int target=nums[i]*(-1);  //目标求和值
if(nums[rl]+nums[rr]==target)  //和为零,则记录数据,并将边界值向中间逼近
{
vector<int> data(3,0);
data[0]=nums[i];
data[1]=nums[rl];
data[2]=nums[rr];
ans.push_back(data);
while(nums[rl]==data[1]&&rl<rr) rl++;  //避免计算重复元素
while(nums[rr]==data[2]&&rl<rr) rr--;
}
else if(nums[rl]+nums[rr]<target)  rl++;
else rr--;
}
}
}
return ans;
}
};


  这种算法的实现思路也算是比较常见的了,需要引起我的注意。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: