Leetcode 18. 四数之和
题目
给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。
注意:
答案中不可以包含重复的四元组。
示例:
给定数组 nums = [1, 0, -1, 0, -2, 2],和 target = 0。
满足要求的四元组集合为:
[
[-1, 0, 0, 1],
[-2, -1, 1, 2],
[-2, 0, 0, 2]
]
来源:力扣(LeetCode).
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
解法
思路一:暴力法
简单易懂枚举所有可能
代码
vector<vector<int>> fourSum(vector<int>& nums, int target) {
if(nums.size()<4) return {};
sort(nums.begin(),nums.end());
set<vector<int>> a;//用来去重
vector<vector<int>> res;
for(int i=0;i<nums.size()-3;i++)
{
if(nums[i]>target&&target>0) break;
for(int j=i+1;j<nums.size()-2;j++)
{
for(int l=j+1;l<nums.size()-1;l++)
{
for(int r=l+1;r<nums.size();r++)
{
if(nums[i]+nums[j]+nums[l]+nums[r]==target)
a.insert(vector<int>{nums[i],nums[j],nums[l],nums[r]});
}
}
}
}
for(auto c:a)
{
res.push_back(c);
}
return res;
}
作者:gpe3DBjDS1
来源:力扣(LeetCode)
链接
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
思路二:双指针
在 3Sum 的基础上再加一层循环即可,3Sum 时间复杂度为 O ( n 2 ) O(n2) O(n2),所以这个方法时间复杂度为 O ( n 3 ) O(n3) O(n3)。
代码
vector<vector<int>> fourSum(vector<int>& nums, int target) {
if(nums.size()<4) return {};
sort(nums.begin(),nums.end());
vector<vector<int>> res;
set<vector<int>> a;//去重
for(int i=0;i<nums.size()-3;i++)
{
if(nums[i]>target&&target>0) break;
for(int j=i+1;j<nums.size()-2;j++)
{
int l=j+1;
int r=nums.size()-1;
while(l<r)
{
if(nums[i]+nums[j]+nums[l]+nums[r]<target)
l++;
else if(nums[i]+nums[j]+nums[l]+nums[r]>target)
r--;
else
{
vector<int> temp{nums[i],nums[j],nums[l],nums[r]};
a.insert(temp);
l++;
r--;
}
}
}
}
for(auto c:a)
{
res.push_back(c);
}
return res;
}
作者:gpe3DBjDS1
来源:力扣(LeetCode)
链接
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
思路三:哈希表
排序
从左遍历,每次遍历进入3
从右遍历,每次进入4
设定左右索引,指向 2、3 还没遍历的数组框左右,判断索引数之和与temp_target,大了右索引左移,小了左索引右移,符合的数放入return。
这个没有写,如果还需要提高时间,那么这样想,因为是4个数之和,可以看成两组数之和,每组两个数,这样如果知道了每两个数之和,问题如上面的转换,这样时间便是O(n^2),不过空间就需要大很多。维持如下的结构:
维持这样的结构,第一行为组的和,然后指向所有的组合,因为c++ map 是会自动排序的,所以创建 map<int,pari<int,int> > > 这样的一个表便可以了,然后就是剩下判断问题了,如只有 -2 0 2 各一个,但是 -2 2 是可以的,所以需要考虑个数问题。
我用了unorder_map,并没有通过双向收缩来实现,所以判断起来比较麻烦,不过map 在初始化的时候,时间需要 l o g n logn logn,所以这样的总体时间是 O ( n 2 l o g n ) O(n^2logn) O(n2logn),这个也是 d i s c u s s discuss discuss 普遍时间。而使用unorder_map,我的时间是 O ( n 2 + n M a x l e n ( b u c k e t ) 2 ) O(n^2 + nMaxlen(bucket)^2) O(n2+nMaxlen(bucket)2),上图就是 M a x l e n ( b u c k e t ) = 3 Maxlen(bucket)=3 Maxlen(bucket)=3,在n较大是较优,注意是+号,毕竟比较难表达,应该会接近 O ( n 2 ) O(n^2) O(n2)。
计算两数之和,放入mp 中
统计各数的个数,使用map<int,int> cnt 存储
遍历mp
判断 target- val1 是否在mp 中,否继续遍历,这个时间是
O
(
1
)
O(1)
O(1)
存在的话,如果
v
a
l
1
>
v
a
l
2
,
c
o
n
t
i
n
u
e
val1 > val2,continue
val1>val2,continue,为了避免重复
遍历bucket1 中的组合
遍历bucket2 中的组合,如果
m
a
x
(
g
r
o
u
p
1
)
<
=
m
i
n
(
g
r
o
u
p
2
)
max(group1)<=min(group2)
max(group1)<=min(group2)则进入下一步,这是为了避免重复,等号为了 0,0,0,0情况
通过cnt 判断数的个数够不够,够的放入return。
结束
代码
vector<vector<int> > fourSum(vector<int> &num, int target) {
34 sort(num.begin(),num.end());
35 vector<vector<int> > ret;
36 unordered_map<int,vector<pair<int,int> > > mp;
37 unordered_map<int,int> cnt;
38 for(unsigned int a=0;a<num.size();){
39 for(unsigned int b=a+1;b<num.size();){
40 mp[num[a]+num[b]].push_back(pair<int,int> {num[a],num[b]});
41 b++;
42 while(b<num.size()&&num[b-1]==num[b]) b++;
43 }
44 a++;
45 while(a<num.size()&&num[a-1]==num[a]) a++;
46 }
47 for(unsigned int a = 0;a<num.size();a++)
48 cnt[num[a]]++;
49 // for(unordered_map<int,int>::iterator it=cnt.begin();it!=cnt.end();it++)
50 // cout<<it->first<<":"<<it->second<<endl;
51 // for(unordered_map<int,vector<pair<int,int> > >::iterator it1=mp.begin();it1!=mp.end();it1++){
52 // cout<<it1->first<<":"<<endl;
53 // for(int i=0;i<it1->second.size();i++)
54 // cout<<it1->second[i].first<<" "<<it1->second[i].second<<endl;
55 // }
56
57 for(unordered_map<int,vector<pair<int,int> > >::iterator it1=mp.begin();it1!=mp.end();it1++){
58 // cout<<it1->first<<endl;
59 unordered_map<int,vector<pair<int,int> > >::iterator it2=mp.find(target - it1->first);
60 if(it2!=mp.end()){
61 // cout<<it1->first<<it2->first<<endl;
62 // cout<<it1->second.size()<<it2->second.size()<<endl;
63 if(it1->first > it2->first) continue;
64 for(int i=0;i<it1->second.size();i++){
65 for(int j=0;j<it2->second.size();j++){
66 int a = it1->second[i].first,b = it1->second[i].second,c = it2->second[j].first,d = it2->second[j].second;
67 if(max(a,b)<=min(c,d)){
68 bool flag = true;
69 cnt[a]--;
70 cnt[b]--;
71 cnt[c]--;
72 cnt[d]--;
73 if(cnt[a]<0||cnt[b]<0||cnt[c]<0||cnt[d]<0) flag = false;
74 cnt[a]++;
75 cnt[b]++;
76 cnt[c]++;
77 cnt[d]++;
78 // cout<<a<<" "<<b<<" "<<c<<" "<<d<<" "<<flag<<endl;
79 if(flag){
80 vector<int> tmp = {a,b,c,d};
81 sort(tmp.begin(),tmp.end());
82 ret.push_back(tmp);
83 }
84 }
85 }
86 }
87 }
88 }
89 return ret;
90 }
思路四:递归
将k个数转化为k-1个数的问题,不断递归,直到两个数。
代码
class Solution {
vector<vector<int> > kSum(vector<int>& nums, int start, int k, int target)
{
vector<vector<int> > res;
int len=nums.size();
if(k==2)
{
int left=start,right=len-1;
int lastleftval=-1,lastrightval=-1;
bool flag=false;// flag=false表示还未满足条件的元祖
while(left<right)
{
if((nums[left]+nums[right])>target)
right--;
else if((nums[left]+nums[right])<target)
left++;
else
{
if(flag==true && lastleftval==nums[left] && lastrightval==nums[right])
{
left++;
right--;
continue;
}
flag=true;
lastleftval=nums[left];
lastrightval=nums[right];
vector<int> subres;
subres.push_back(nums[left]);
subres.push_back(nums[right]);
res.push_back(subres);
left++;
right--;
}
}
return res;
}
int lastval=-1;
for(int i=start; i<len; i++)
{
if(i!=start && nums[i]==nums[i-1])
continue;
vector<vector<int> > subres= kSum(nums, i+1, k-1, target-nums[i]);
int sublen=subres.size();
for(int j=0; j<sublen; j++)
{
vector<int>temp;
temp.push_back(nums[i]);
for(int j1=0; j1<k-1; j1++)
temp.push_back(subres[j][j1]);
res.push_back(temp);
}
}
return res;
}
public:
vector<vector<int> > fourSum(vector<int>& nums, int target) {
vector<vector<int> > res;
int len=nums.size();
if(len==0)
return res;
sort(nums.begin(),nums.end());
return kSum(nums,0,4,target);
}
};
来源:链接