①思路(本质)
1.本题的本质:三数之和
2.思路
三数之和的代码基础上外加一层循环,就是四数之和!
三数之和变量:i、left、right
四数之和变量:j、i、left、right
四数之和变量:j、三数之和变量(从嵌套视角看待)
②代码分步实现(不要多功能同时实现)
同样的先考虑存储结果集,再进行去重
代码跟复制粘贴差不多了!只是去重优化了一下
1.先实现"存储结果集"
本题可以做二级剪枝!
小经验:双指针外面有多少层循环,就可以做多少级剪枝!
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>>result;
sort(nums.begin(),nums.end());//双指针做类似于二分查找的操作,一定要排序
//三数之和外嵌套一层代码
for(int j=0;j<nums.size()-3;j++){//for(int j=0;j<nums.size()-3;j++){//这是我写的初代版本,但是需要分类讨论//所以改成了更一般化的情况
if(nums[j]>=0&&nums[j]>target)break;
//下面就是三数之和的代码
for(int i = j+1;i<nums.size();i++){//for(int i = j+1;i<nums.size()-2;i++){//这也是我的写的初代版本
if (nums[j] + nums[i] > target && nums[j]>= 0) {
break;
}
int left = i+1;
int right = nums.size()-1;
while(left<right){
int sum = nums[j]+nums[i]+nums[left]+nums[right];
if(sum==target){
result.push_back({nums[j],nums[i],nums[left],nums[right]});
left++;
}
else if(sum<target){ left++; }
else{ right--; }
}
}
}
return result;
}
};
2.实现去重操作(两种方式:set容器、双指针去重)
具体看文章下面的代码实现,整体思路都跟三数之和一样!
③补充本题细节
1.本题相比三数之和修改了for循环的终止范围
本题元素的个数并不是至少4个。(可能只有一个元素,但要求四数之和!)
nums.size()-3。因为返回值是无符号类型,所以元素太少返回的值是巨大的。终止条件就不对!
改成nums.size()通用。(如果分类讨论nums.size()-3也行,如果元素小于4个就直接返回空二维数组)
2.本题案例累加完超过int取值范围,需要转化为long类型!
④具体代码实现
1.set容器去重方式实现(优点同三数之和,实现巨方便,性能比较差)
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
set<vector<int>>result;
sort(nums.begin(),nums.end());//双指针做类似于二分查找的操作,一定要排序
for(int i=0;i<nums.size();i++){
if(nums[i]>=0&&nums[i]>target)break;
for(int j = i+1;j<nums.size();j++){
if (nums[j] + nums[i] > target && nums[j]>= 0) {
break;
}
int left = j+1;
int right = nums.size()-1;
while(left<right){
long sum = (long)nums[i]+nums[j]+nums[left]+nums[right];
if(sum==target){
result.insert({nums[i],nums[j],nums[left],nums[right]});
left++;
}
else if(sum<target){ left++; }
else{ right--; }
}
}
}
return {result.begin(),result.end()};
}
};
2.双指针去重(优点同三数之和,实现不方便,性能高)
需要对四个元素去重
亲手写出三数之和去重的双指针代码,四数之和也如同照葫芦画瓢!
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>>result;
sort(nums.begin(),nums.end());//双指针做类似于二分查找的操作,一定要排序
//三数之和外嵌套一层代码
for(int j=0;j<nums.size();j++){//for(int j=0;j<nums.size()-3;j++){//这是我写的初代版本,但是需要分类讨论//所以改成了更一般化的情况
if(nums[j]>=0&&nums[j]>target)break;//一级剪枝
//这就是三数之和的代码
for(int i = j+1;i<nums.size();i++){//for(int i = j+1;i<nums.size()-2;i++){//这也是我的写的初代版本
if (nums[j] + nums[i] > target && nums[j]>= 0) {//二级剪枝
break;
}
int left = i+1;
int right = nums.size()-1;
while(left<right){
long sum =(long) nums[j]+nums[i]+nums[left]+nums[right];//四个数案例存在过大的情况,需要转化成(long类型)
if(sum==target){
result.push_back({nums[j],nums[i],nums[left],nums[right]});
while(left<right&&nums[left]==nums[left+1]){ left++; }//left去重
while(left<right&&nums[right]==nums[right-1]){ right--; }//right去重
left++;
right--;
}
else if(sum<target){ left++; }
else{ right--; }
}
//i变量去重
while(i<nums.size()-1&&nums[i]==nums[i+1]){ i++; }//这里不能i<nums.size()//防止数组越界
}
//j变量去重(你应该体会到了,只是嵌套一层套路而已!
while(j<nums.size()-1&&nums[j]==nums[j+1]){ j++; }//这里不能i<nums.size()//防止数组越界
}
return result;
}
};
⑤拓展
可以拓展到N数之和,到时候使用回溯算法为双指针嵌套循环即可!
感兴趣的朋友可以上b站了解一下回溯算法!也可以上代码随想录
⑥具体讲讲我的去重逻辑!
双指针内的去重比较容易,也跟外层去重差不多。所以我只讲外层去重
以下图去重代码为例,为变量 j 去重
执行完去重后(刚好完成while时),
(如果可以去重)其实 j 指向最后一个可去重的元素(没完全去重),
但是: j 此时处于循环体的尾部,不会匹配目标值,要进入下一个循环才会匹配,而进入下一个循环就真正的做到去重!,然后执行匹配是否满足四数之和代码!
举例:-4,-4,-4,-4,0,1,2,3。
当j = 0,(易得:其他三个变量恒定不变)
加入结果集之后开始执行去重,最终:
j=3,此时nums[j]=4。nums[j+1]=0。(循环体尾部,所以不会匹配target值)
进入下一次循环 j=4,nums[j]=0,然后匹配是否sum ==target值(达到去重目的)