首先明确一点,这个方面的问题设计到的知识点是数组的查找的问题。对于类似的这样的查找操作的具体办法就是三种解决方法:
1.暴力算法,多个for循环,很高的时间复杂度 2.先排序,然后左右夹逼,但是这样会破坏原始数组的下表 3.利用Hash表,直接定位元素,很少的时间复杂度
TwoSum
先来看看最简单的,在一个数组中找两个数的和等于某个数。
这个题目最简简单的方法就是暴力法,所需的时间复杂度是O(n2),但是这是不允许的,所以一个O(n)的方法就是利用Hash表存储数据,这样能够把查找的时间降低下来。使用到的工具就是unordered_map。在这个hash表中,key是数组的数字本身,value是数组数字的下标值。这样只需要把原数组扫描一遍,对于每一个数组中的值,求target与数组元素的差值,然后把这个差值作为key到hash表中找对应的value。
但是注意这样的值:
3 2 4 target=6
这样会产生三组值满足:(3,3)(2,4)(4,2)所以要规定一下:第二个通过hash得到的下标值一定要比第一个下标值大才可以。
vector<int> twoSum(vector<int>& nums, int target) { unordered_map<int, int> mapping; vector<int> result; for(int i = 0; i < nums.size(); i++) { mapping[nums[i]] = i; } for(int i = 0; i < nums.size(); i++) { const int gap = target - nums[i]; if(mapping.find(gap) != mapping.end() && mapping[gap] > i) { result.push_back(i + 1); result.push_back(mapping[gap] + 1); } } return result; }
find函数,在找到的时候会返回一个指向该元素的iterator,如果没有找到会返回一个end。如果找到了,可以通过operator[]来访问这个元素。
对于2-sum的算法,暴力算法的时间复杂度是O(n2),Hash表的时间复杂度是O(n),排序然后两边夹逼的时间复杂度是排序的时间复杂度加上左右夹逼的时间复杂度:O(NlogN)+O(N)=O(NlogN)。
ThreeSum
对于三个数的和问题,暴力的算法就是使用三个for循环,这样的话时间复杂度是O(n3),所以一个好的改进就是先对原始的数组排序,然后使用两边夹逼的方法。但是要注意一些细节的问题:
1.具体的实现方法是,先固定一个,然后就成了2Sums的两边夹逼方法。
2.原始的数组是允许重复的,但是得到的solution是不允许重复的,所以要做一些合理的去重处理。防止-1,-1,-1,2,2这样的数组会得到-1,-1,2和-1,-1,2两组一样的结果。
vector<vector<int> > threeSum(vector<int>& nums, int target) { vector<vector<int> > result; if(nums.size() < 3) return result; for(int i = 0; i < nums.size() - 2; i++) { int j = i + 1; int k = nums.size() - 1; if(i > 0 && nums[i] == nums[i-1]) continue; while(j < k) { int sum = nums[i] + nums[j] + nums[k]; if(sum < target) { j++; } else if(sum > target) { k--; } else { vector<int> temp; temp.push_back(nums[i]); temp.push_back(nums[j]); temp.push_back(nums[k]); result.push_back(temp); j++; k--; //去重,防止最后的结果有重复的三元组 while(nums[j] == nums[j-1] && nums[k] == nums[k+1] && j < k) { j++; } } }//while }//for return result; }
算法的时间复杂度是O(NlogN)+O(N2),所以总的时间复杂度是O(N2)。
ThreeSumClosest
再看一个很类似的题目:找最接近给定值的那三个数,并输出这三个数的和。
用的方法仍然是先排序,然后利用两边夹逼的方法。
int threeSumClosest(vector<int>& nums, int target) { int min_gap = 65535; int result; sort(nums.begin(), nums.end()); for(int i = 0; i < nums.size() - 2; i++) { int k = nums.size() - 1; int j = i + 1; while(j < k) { int sum = nums[i] + nums[j] + nums[k]; int gap = abs(sum - target); if(gap < min_gap) { result = sum; min_gap = gap; } if(sum < target) { j++; } else { k--; } }//while }//for return result; }
FourSum
这个问题还是延续前面的那种方法,先排序,然后利用左右夹逼。这样的话排序的时间复杂度是O(NlogN),左右夹逼的时间复杂度是O(N3),所以总的时间复杂度是O(N3)。但是这个FourSum比其他的难点在于:要仔细的对待去重的问题,不然会得到很多重复一样的答案。
vector<vector<int> > fourSum(vector<int>& nums, int target) { vector<vector<int> > result; if(nums.size() < 4) return result; sort(nums.begin(), nums.end()); for(int i = 0; i < nums.size() - 3; i++) { if(i > 0 && nums[i] == nums[i-1]) continue; for(int j = i + 1; j < nums.size() - 2; j++) { if(j > 1 && nums[j] == nums[j - 1]) continue; int l = j + 1; int k = nums.size() - 1; while(l < k) { int sum = nums[i] + nums[j] + nums[l] + nums[k]; if(sum < target) { l++; } else if(sum > target) { k--; } else { vector<int> tmp; tmp.push_back(nums[i]); tmp.push_back(nums[j]); tmp.push_back(nums[l]); tmp.push_back(nums[k]); result.push_back(tmp); l++; k--; while(nums[l]==nums[l-1]&&nums[k]==nums[k+1]&&l<k) l++; } }//while }//for }//for }
对于4-sum的算法其实可以用hash表做一个优化,就是先用hash表存元数组中的任意两个元素的和,然后在对这个新的hash使用2-sum的线性查询,所以总的时间复杂度是O(N2)。具体的算法分析这里分享一个连接:
烟客旅人:http://tech-wonderland.net/blog/summary-of-ksum-problems.html