[LeetCode] 4Sum
Given an array S of n integers, are there elements a, b, c, and d in S such that a + b + c + d = target? Find all unique quadruplets in the array which gives the sum of target.
Note:
- Elements in a quadruplet (a,b,c,d) must be in non-descending order. (ie, a ≤ b ≤ c ≤ d)
- The solution set must not contain duplicate quadruplets.
For example, given array S = {1 0 -1 0 -2 2}, and target = 0. A solution set is: (-1, 0, 0, 1) (-2, -1, 1, 2) (-2, 0, 0, 2)
分析:
方法一:
类似3Sum,先排序,这后两重循环,然后对最后的一个数组设两个指针遍历。这里注意重复的问题,复杂度 排序O(log n)+遍历O(n^3)
code如下
1 class Solution { 2 private: 3 vector<vector<int> > ret; 4 public: 5 vector<vector<int> > fourSum(vector<int> &num, int target) { 6 // Start typing your C/C++ solution below 7 // DO NOT write int main() function 8 sort(num.begin(), num.end()); 9 10 ret.clear(); 11 12 for(int i = 0; i < num.size(); i++) 13 { 14 //skip dupliated 15 if (i > 0 && num[i] == num[i-1]) 16 continue; 17 18 for(int j = i + 1; j < num.size(); j++) 19 { 20 //skip dupliated 21 if (j > i + 1 && num[j] == num[j-1]) 22 continue; 23 24 int k = j + 1; 25 int m = num.size() - 1; 26 27 while(k < m) 28 { 29 if (k > j + 1 && num[k] == num[k-1]) 30 { 31 //skip dupliated 32 k++; 33 continue; 34 } 35 36 if (m < num.size() - 1 && num[m] == num[m+1]) 37 { 38 //skip dupliated 39 m--; 40 continue; 41 } 42 43 int sum = num[i] + num[j] + num[k] + num[m]; 44 45 if (sum == target) 46 { 47 vector<int> tmp; 48 tmp.push_back(num[i]); 49 tmp.push_back(num[j]); 50 tmp.push_back(num[k]); 51 tmp.push_back(num[m]); 52 ret.push_back(tmp); 53 k++; 54 } 55 else if (sum < target) 56 k++; 57 else 58 m--; 59 } 60 } 61 } 62 63 return ret; 64 } 65 };
方法二:那么时间复杂度能不能更好呢?其实我们可以考虑用二分法的思路,如果把所有的两两pair都求出来,然后再进行一次Two Sum的匹配,我们知道Two Sum是一个排序加上一个线性的操作,并且把所有pair的数量是O((n-1)+(n-2)+...+1)=O(n(n-1)/2)=O(n^2)。所以对O(n^2)的排序如果不用特殊线性排序算法是O(n^2*log(n^2))=O(n^2*2logn)=O(n^2*logn),算法复杂度比上一个方法的O(n^3)是有提高的。
思路虽然明确,不过细节上会多很多情况要处理。首先,我们要对每一个pair建一个数据结构来存储元素的值和对应的index,这样做是为了后面当找到合适的两对pair相加能得到target值时看看他们是否有重叠的index,如果有说明它们不是合法的一个结果,因为不是四个不同的元素。接下来我们还得对这些pair进行排序,所以要给pair定义comparable的函数。最后,当进行Two Sum的匹配时因为pair不再是一个值,所以不能像Two Sum中那样直接跳过相同的,每一组都得进行查看,这样就会出现重复的情况,所以我们还得给每一个四个元素组成的tuple定义hashcode和相等函数,以便可以把当前求得的结果放在一个HashSet里面,这样得到新结果如果是重复的就不加入结果集了。
如果使用的是基于红黑树的map,查找效率O(logn),基于hash的map,查找复杂度是O(n),unordered_multimap是基于C++11的hash map
第二种方法比第一种方法时间上还是有提高的,其实这道题可以推广到k-Sum的问题,基本思想就是和第二种方法一样进行二分,然后两两结合,不过细节就非常复杂了(这点从上面的第二种解法就能看出来),所以不是很适合在面试中出现,有兴趣的朋友可以进一步思考或者搜索网上材料哈
code如下
1 class Solution { 2 public: 3 vector<vector<int>> fourSum(vector<int>& num, int target) { 4 vector<vector<int>> result; 5 if (num.size() < 4) return result; 6 sort(num.begin(), num.end()); 7 unordered_multimap<int, pair<int, int>> cache;// 基于哈希,可以存储多个相同的key 值 8 for (int i = 0; i + 1 < num.size(); ++i) //保存所有的两两配对 9 for (int j = i + 1; j < num.size(); ++j) 10 cache.insert(make_pair(num[i] + num[j], make_pair(i, j))); 11 for (unordered_multimap<int, pair<int, int>>::iterator i = cache.begin(); i != cache.end(); ++i) { 12 int x = target - i->first;//计算另外的数 13 pair<unordered_multimap<int, pair<int, int>>::iterator,unordered_multimap<int, pair<int, int>>::iterator> range; 14 //auto range 15 range = cache.equal_range(x);//在cache中查找 16 //返回的i,j就是指向满足条件的首位迭代器 17 for (unordered_multimap<int, pair<int, int>>::iterator j = range.first; j != range.second; ++j) { 18 int idx1 = i->second.first; 19 int idx2 = i->second.second; 20 int idx3 = j->second.first; 21 int idx4 = j->second.second; 22 if (idx1 != idx3 && idx1 != idx4 && idx2 != idx3 && idx2 != idx4) { 23 vector<int> vec = { num[idx1], num[idx2], num[idx3], num[idx4] }; 24 sort(vec.begin(), vec.end()); 25 result.push_back(vec); 26 } 27 } 28 } 29 sort(result.begin(), result.end()); 30 result.erase(unique(result.begin(), result.end()), result.end()); 31 /* 32 * unique(result.begin(), result.end()) 将result unique化,返会的是不重复的最后一个元素下一个的迭代器, 33 * erase将后续重复的内容擦除 34 */ 35 return result; 36 } 37 };