一、概述
给出一个序列nums和一个和target,在数列中找出四个数字,它们的和为target。输出所有的这四个数字。
可以说是之前的3sum的拓展版。
我的思路就是暴力求解,因此时间复杂度没得卡看,空间复杂度还可以。
二、分析
1、我的方法
由于需要四个数字,因此我们维护四个指针即可。通过以下措施来减少运算:
将序列由小到大排序,四个指针c1、c2、c3、c4开始指向头四个元素,然后只动c4,求和,若和小于target,c4向后移动;若和大于target,则由于序列有序,后面的数字的和一定也大于target,直接break;若等于target,考虑到重复元素,按如下思路进行:
考察四个指针指向的元素,将其与其后面的元素比较,若与后面的元素相等,则指针后移,否则不后移,直到所有指针指向的元素与其后面的元素不相等,将这四个元素组成vector,压入result。
然后c3动,c4此时从c3后一个元素开始动。
当c3指向尾二元素后,c2动,c2动之后c3指向c2后一个元素。以此类推。
举例可以是六个零,即000000,target为0。
开始四个指针指向头四个0,计算可知符合其和为0,因此检查是否和后面的元素相等,均相等,所有指针都指向最后一个0,然后保存结果。保存之后直接循环退出。
这样可以防止有多余的结果。
代码如下:
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>> result;
int Num=nums.size();
if(Num<4)
return result;
sort(nums.begin(),nums.end());
int c1,c2,c3,c4;
for(c1=0;c1<Num-3;++c1)
for(c2=c1+1;c2<Num-2;++c2)
for(c3=c2+1;c3<Num-1;++c3)
for(c4=c3+1;c4<Num;++c4)
{
if(nums[c1]+nums[c2]+nums[c3]+nums[c4]<target)
continue;
else if(nums[c1]+nums[c2]+nums[c3]+nums[c4]==target)
{
while(c1<Num-4&&nums[c1]==nums[c1+1])
++c1;
while(c2<Num-3&&nums[c2]==nums[c2+1])
++c2;
while(c3<Num-2&&nums[c3]==nums[c3+1])
++c3;
while(c4<Num-1&&nums[c4]==nums[c4+1])
++c4;
vector<int> tmp;
tmp.push_back(nums[c1]);
tmp.push_back(nums[c2]);
tmp.push_back(nums[c3]);
tmp.push_back(nums[c4]);
result.push_back(tmp);
break;
}
else
break;
}
return result;
}
};
2、较好的方法
我们先回忆一下3sum的思路:
固定第一个指针;然后第二个指针指向第一个指针的下一个,第三个指针指向最后。如果三个数的和大于target,那第三个指针向前;小于则第二个指针向后。保证第二个指针和第三个指针不相遇,则能够找出所有的结果。
对于相同的结果,先考虑第二个和第三个指针在移动前后指向了相同的元素。很简单,再次移动即可。
再考虑第一个指针指向的数字有重复的情况,即第一个指针目前指向的元素和下一个指向的元素有重复。
考虑如下情况:第一个指针指向第a个元素,第二个指向第b个元素,第三个指向第c个元素。
现在有a元素与a+1元素相同。那么如果a+1=b,即我们将【a,a+1,c】压入result,则下一次当a移动到a+1的时候,这一结果必不存在。如果a+1!=b,即我们将【a,b,c】压入result,则下一次将a移动到a+1的时候,结果是【a+1,b,c】,出现重复。因此若a+1=b,无论出现何种情况,都可以直接将a+1跳过而不会漏掉正确结果,也不会有重复结果。
因此可以得到最终答案。
当3sum扩展到4sum,情况类似。我们的目的仍然是要得到两个指针,第一个向后,第二个向前。因此我们要先确定前两个指针的位置,然后后两个指针互相逼近即可。前两个指针的操作与3sum中第一个指针的操作类似:
首先,考虑第二个指针,要想让第三个指针和第四个指针能够相互靠近,则必须保证第三个指针和第四个指针在第二个指针的后一位和后二位时,它们的和小于target,因为此时和最小;同理要保证第三个指针和第四个指针在尾一和尾二时,它们的和大于target,因为此时和最大。这样才能够进行之后的操作。
为了筛除重复的选项,当第二个指针的指向与它前一个相同时,也可以直接跳过。
然后考虑第一个指针,同样的,为了第三个指针和第四个指针能够靠近,要满足第一个指针及其后三个元素的和小于target,第一个指针及尾部三个元素的和大于target,也要筛除重复的选项。
这就是整体的思路。
再来捋一遍:思路就是对于第一个和第二个指针,保证它们在确定位置后的和的最小值小于target,和的最大值大于target,然后移动第三个和第四个指针。
与我的思路的最大不同有两处,第一个是它判断和与target的关系在第一重循环,这样极大减少了循环量,第二个则是它的第三个和第四个指针分别向前向后,比我的效率高。
代码如下:
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>> result;
int Num=nums.size();
if(Num<4)
return result;
sort(nums.begin(),nums.end());
int c1,c2,c3,c4;
for(c1=0;c1<Num-3;++c1)
{
if(c1>0&&nums[c1]==nums[c1-1])
continue;
if(nums[c1]+nums[c1+1]+nums[c1+2]+nums[c1+3]>target)
break;
if(nums[c1]+nums[Num-3]+nums[Num-2]+nums[Num-1]<target)
continue;
for(c2=c1+1;c2<Num-2;++c2)
{
if(c2>c1+1&&nums[c2]==nums[c2-1])
continue;
if(nums[c1]+nums[c2]+nums[c2+1]+nums[c2+2]>target)
break;
if(nums[c1]+nums[c2]+nums[Num-2]+nums[Num-1]<target)
continue;
c3=c2+1;
c4=Num-1;
while(c4>c3)
{
int sum=nums[c1]+nums[c2]+nums[c3]+nums[c4];
if(sum>target)
c4--;
else if(sum<target)
c3++;
else
{
result.push_back(vector<int>{nums[c1],nums[c2],nums[c3],nums[c4]});
do{c3++;}while(nums[c3-1]==nums[c3]&&c3<c4);
do{c4--;}while(nums[c4+1]==nums[c4]&&c3<c4);
}
}
}
}
return result;
}
};
三、总结
忘了3sum的原理,所以做的很蠢。问题的拓展与延伸需要深入的思考才行。关键在于和的最值与target的关系。
对于xsum问题,先确定x-2个指针的位置,根据和的大小确定,对于最后两个向中间逼近即可。