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; } };
这种算法的实现思路也算是比较常见的了,需要引起我的注意。
相关文章推荐
- leetcode题目之两数求和
- 167.leetcode Two Sum II - Input array is sorted(medium)[两数求和固定值]
- LeetCode Divide Two Integers 不使用除号取模乘号实现两数相除
- 【leetcode74】Sum of Two Integers(不用+,-求两数之和)
- C++第10周项目1扩展参考——求和
- LeetCode - 两数之和
- leetcode之数组中找两数和为指定值
- [leetcode, python] Two Sum 两数之和等于某数
- Leetcode - Math -258. Add Digits(数位求和,规律题)
- 求和问题总结(leetcode 2Sum, 3Sum, 4Sum, K Sum)
- LeetCode - 66/67 - 两数相加加法模拟
- [LeetCode] Divide Two Integers 两数相除
- leetcode 690. Employee Importance 员工重要性求和 + 深度优先遍历DFS
- LeetCode-1 两数之和
- leetcode之链表类之链表归并类-----OJ 2/21/23/445 链表相加求和 链表归并
- leetcode——Add Two Numbers 两个链表表示的正整数对其求和(AC)
- [LeetCode] Add Two Numbers 两数相加
- 【leetcode74】Sum of Two Integers(不用+,-求两数之和)
- 【leetcode】第15题:3sum(medium)
- C++第10周项目2扩展之2参考——迭代求和