【算法】2SUM/3SUM/4SUM问题
2017-04-26 01:21
239 查看
之前就总结过一些Leetcode上各种sum问题,今天再拿出来完整得总结一番。
nSUM问题是指,在一个数组中,找出n个数相加和等于给定的数,这个叫做nSUM问题。
常见的有2SUM,3SUM,4SUM问题,还有各种SUM问题的变种.
Leetcode上SUM问题包括:
1. 2SUM
15. 3Sum
16. 3Sum Closest
18. 4Sum
454. 4Sum II
这道题有两种思路:
1. 一种思路从首尾搜索两个数的和,并逐步逼近。
2. 另外一种思路是固定一个数A,看SUM-A是否在这个数组之中。
设置两个指针,一个指向数组开头,一个指向数组结尾,从两边往中间走。直到扫到满足题意的为止或者两个指针相遇为止。
此时这种搜索方法就是类似于杨氏矩阵的搜索方法,就是从 杨氏矩阵的左下角开始搜索,直到找到为止。
如果此时题目条件变为如果没有找出最接近的2SUM和问题,解决方法跟上面是一样的
用这种方法2SUM问题的时间复杂度是O(nlogn)的,主要在于排序的时间。
代码如下【注意因为题目中说一定有解所以才下面这样写,如果不一定有解,则还要再加一些判断】
这种方法的时间复杂度为O(n)
第二种方法,时间复杂度低,但是需要处理重复情况,略麻烦
之后的数组中转化为找和为target-nums[i]的2SUM问题。
因为此时排序复杂度在3SUM问题中已经不占据主要复杂度了,所以直接采用思路1的方法就好。
对于3SUM问题,上面这个解法的时间复杂度为O(n2)
其实对于3SUM问题,另外一种求解思路就是,先预处理一遍数组中两两相加的结果,然后再遍历每一个数
1. 先遍历第一个数,然后固定第一个数之后,转化为剩下元素的3SUM问题
2. 先遍历两个数,求和,然后转化为剩下元素的2SUM问题
具体写出来的形式,可以写成最外层两个循环,即固定为两个数之后,再化为2SUM。
代码如下【这个代码我是抄的Discuss里的,我自己的是层层调用嵌套形式的写法,运行时间有点长】
但是此时需要有一个判重的问题,所以需要map中存如下数
然后再判重。
因为需要判重,所以最糟糕情况下其时间复杂度为O(n4)
那么如果没有判重问题,是否就可以解决了呢?
那就是454. 4Sum II问题
这道题是在四个数组中,各选出一个数,使其和为某一定值。
则可以按照上述方法,讲前两个数组所有可能的和做一个map,然后遍历后两个数组所有可能的和,所以这个是O(n2)的算法。
比如对于6SUM问题,先用一个O(n3)的方法,将其中所有三个数相加的可能的情况都保存下来,这一步的时间复杂度是O(n3)。接下来用两种方法都行:
1. 遍历三个数,然后看剩下的和是否在保存的可能性中。这一步的时间复杂度是O(n3)
2. 直接在保存的可能性中遍历,遍历到SUM1之后,看target-SUM1是否也在这个可能性中。这一步的时间复杂度是O(n2)
nSUM问题是指,在一个数组中,找出n个数相加和等于给定的数,这个叫做nSUM问题。
常见的有2SUM,3SUM,4SUM问题,还有各种SUM问题的变种.
Leetcode上SUM问题包括:
1. 2SUM
15. 3Sum
16. 3Sum Closest
18. 4Sum
454. 4Sum II
2SUM问题
最常见的是2SUM问题(1. 2SUM),就是数组中找出两个数相加和为给定的数。这道题有两种思路:
1. 一种思路从首尾搜索两个数的和,并逐步逼近。
2. 另外一种思路是固定一个数A,看SUM-A是否在这个数组之中。
对于第一种思路如下:
此方法是先将数组从小到大排序设置两个指针,一个指向数组开头,一个指向数组结尾,从两边往中间走。直到扫到满足题意的为止或者两个指针相遇为止。
此时这种搜索方法就是类似于杨氏矩阵的搜索方法,就是从 杨氏矩阵的左下角开始搜索,直到找到为止。
如果此时题目条件变为如果没有找出最接近的2SUM和问题,解决方法跟上面是一样的
用这种方法2SUM问题的时间复杂度是O(nlogn)的,主要在于排序的时间。
第二种思路方法如下:
对数组中的每个数建立一个map/hash_map 然后再扫一遍这个数组,判断target-nums[i]是否存在,如果存在则说明有,不存在继续找。当然这样做的话,需要处理一个细节:判重的问题。代码如下【注意因为题目中说一定有解所以才下面这样写,如果不一定有解,则还要再加一些判断】
vector<int> twoSum(vector<int>& nums, int target) { unordered_map<int,vector<int>> mark; for(int i=0;i<nums.size();i++) mark[nums[i]].push_back(i); for(int i = 0;i<nums.size();i++){ if(target-nums[i] == nums[i]){ if(mark[nums[i]].size() > 1){ vector<int> tmp{i,mark[nums[i]][1]}; return tmp; } }else{ if(mark.find(target-nums[i]) != mark.end()){ vector<int> tmp{i,mark[target-nums[i]][0]}; return tmp; } } } }
这种方法的时间复杂度为O(n)
比较一下这两个方法:
第一种方法的思路还是比较好的,鲁棒性好,而且写起来比较容易,但是因为预处理——排序的时间复杂度占了大头,所以其总时间复杂度为O(nlogn)第二种方法,时间复杂度低,但是需要处理重复情况,略麻烦
3SUM问题
然后对于3 Sum问题,解决方法就是最外层遍历一遍,等于选出一个数,之后的数组中转化为找和为target-nums[i]的2SUM问题。
因为此时排序复杂度在3SUM问题中已经不占据主要复杂度了,所以直接采用思路1的方法就好。
void two_sum(vector<int>& nums,int i,int target,vector<vector<int>> &result){ int j = nums.size()-1; int b = i-1; while(i<j){ if(nums[i]+nums[j] == target){ result.push_back(vector<int>{nums[b],nums[i],nums[j]}); //处理重复的情况 i++; j--; while(i<j && nums[i] == nums[i-1]) i++; while(i<j && nums[j+1] == nums[j]) j--; }else{ if(nums[i]+nums[j] < target) i++; else j--; } } return; } vector<vector<int>> threeSum(vector<int>& nums) { set<vector<int>> result; vector<vector<int>> result2; sort(nums.begin(),nums.end()); for(int i=0;i<nums.size();i++) if(i>0&&nums[i-1]== nums[i]) continue; else two_sum(nums,i+1,-nums[i],result2); return result2; }
对于3SUM问题,上面这个解法的时间复杂度为O(n2)
其实对于3SUM问题,另外一种求解思路就是,先预处理一遍数组中两两相加的结果,然后再遍历每一个数
nums[i],判断
target-nums[i]是否在预处理的那个和中,不过这种方法的复杂度也是O(n2)主要是预处理的复杂度。
4SUM问题
对于4Sum问题又衍生出了两种思路:1. 先遍历第一个数,然后固定第一个数之后,转化为剩下元素的3SUM问题
2. 先遍历两个数,求和,然后转化为剩下元素的2SUM问题
第一种思路
其算法复杂度是稳定的O(n3),最外层遍历一遍O(n),然后转为3SUM问题之后又是O(n2)。这种方法相当于4SUM调用3SUM,然后3SUM再调用2SUM,这样函数调用有点多,不方便具体写出来的形式,可以写成最外层两个循环,即固定为两个数之后,再化为2SUM。
代码如下【这个代码我是抄的Discuss里的,我自己的是层层调用嵌套形式的写法,运行时间有点长】
vector<vector<int>> fourSum(vector<int>& nums, int target) { vector<vector<int>> total; int n = nums.size(); if(n<4) return total; sort(nums.begin(),nums.end()); for(int i=0;i<n-3;i++) { if(i>0&&nums[i]==nums[i-1]) continue; if(nums[i]+nums[i+1]+nums[i+2]+nums[i+3]>target) break; if(nums[i]+nums[n-3]+nums[n-2]+nums[n-1]<target) continue; for(int j=i+1;j<n-2;j++) { if(j>i+1&&nums[j]==nums[j-1]) continue; if(nums[i]+nums[j]+nums[j+1]+nums[j+2]>target) break; if(nums[i]+nums[j]+nums[n-2]+nums[n-1]<target) continue; int left=j+1,right=n-1; while(left<right){ int sum=nums[left]+nums[right]+nums[i]+nums[j]; if(sum<target) left++; else if(sum>target) right--; else{ total.push_back(vector<int>{nums[i],nums[j],nums[left],nums[right]}); do{left++;}while(nums[left]==nums[left-1]&&left<right); do{right--;}while(nums[right]==nums[right+1]&&left<right); } } } } return total; }
第二种思路
因为本质上我们是最外层两个循环之后,找是否有target-now的值,我们可以事先做好预处理,即空间换时间,先循环两次,找出两个数所有可能的和,存到map里(这里可以unordered_map)。这两等于是两个O(n2)的时间复杂度相加和,所以最后时间复杂度为O(n2);但是此时需要有一个判重的问题,所以需要map中存如下数
mp[num[i]+num[j]].push_back(make_pair(i,j));
然后再判重。
typedef pair<int,int> pii ; vector<vector<int>> fourSum(vector<int>& nums, int target) { unordered_map<int,vector<pii>> mark; set<vector<int>> res; vector<vector<int>> res2; if(nums.size()<4) return res2; //这个地方也可以不用排序的,排序是因为减少一些计算量,方便下面的循环判定提前跳出条件 sort(nums.begin(),nums.end()); for(int i=0;i<nums.size();i++) for(int j=i+1;j<nums.size();j++) mark[nums[i]+nums[j]].push_back(make_pair(i,j)); //注意注意这个地方有一个巨大的坑,中间的判断条件: i<nums.size()-3,会陷入到死循环中 //因为nums.size()是一个unsigned的类型,其与int相运算,得到的还是unsigned!!!!! //所以如果nums.size()<3的话就会出现死循环,切记切记 for(int i=0;i<nums.size()-3;i++){ //先判定,提前跳出的情况 if(nums[i]+nums[i+1]+nums[i+2]+nums[i+3]>target) break; for(int j=i+1;j<nums.size()-2;j++){ if(mark.find(target-(nums[i]+nums[j])) != mark.end()){ for(auto &&x:mark[target-(nums[i]+nums[j])]){ if(x.first>j){ vector<int> tmp{nums[i],nums[j],nums[x.first],nums[x.second]}; res.insert(tmp); } } } } } for(auto &&x:res){ res2.push_back(x); } return res2; }
因为需要判重,所以最糟糕情况下其时间复杂度为O(n4)
那么如果没有判重问题,是否就可以解决了呢?
那就是454. 4Sum II问题
这道题是在四个数组中,各选出一个数,使其和为某一定值。
则可以按照上述方法,讲前两个数组所有可能的和做一个map,然后遍历后两个数组所有可能的和,所以这个是O(n2)的算法。
nSUM问题的推广和复杂的分析
按照上面的算法复杂度思路,4~6SUM问题,都可以用O(n3)来解决。比如对于6SUM问题,先用一个O(n3)的方法,将其中所有三个数相加的可能的情况都保存下来,这一步的时间复杂度是O(n3)。接下来用两种方法都行:
1. 遍历三个数,然后看剩下的和是否在保存的可能性中。这一步的时间复杂度是O(n3)
2. 直接在保存的可能性中遍历,遍历到SUM1之后,看target-SUM1是否也在这个可能性中。这一步的时间复杂度是O(n2)
相关文章推荐
- [Leetcode][求和问题2Sum/3Sum/4Sum/KSum]相关题目汇总/分析/总结
- 2Sum,3Sum,4Sum问题总结
- 3Sum,4Sum问题
- 2Sum 3Sum 4Sum
- 2016/10/28 很久没更了 leetcode解题 3sum问题进阶版4sum
- 算法设计——解决 3Sum 以及 3Sum Closest 问题
- 算法-3Sum问题
- 2sum/3sum/ksum 问题
- leetcode -- 15. 3Sum 【问题转化2sum + 避免重复计算的方法(规定次序)】
- 2Sum,3Sum问题
- leetcode 15. 3Sum 以及2Sum的问题的处理和求解
- 2sum问题和3sum问题
- array问题---2Sum、3Sum、4Sum
- 从最大子段和问题看算法的优化问题
- 变态题大串烧:微软面试问题 -- 六.算法题——实学考验
- 二分图最大匹配问题匈牙利算法
- 伴随开发人员成长的问题:工程重要,还是算法重要?细节重要,还是架构重要?
- LCS问题算法之VB.net版
- 算法讨论:哲学家就餐问题
- 伴随开发人员成长的问题:工程重要,还是算法重要?细节重要,还是架构重要?