N数之和==target的思路关键点有以下几个:
1.先排序,固定一个元素,利用指针移动其余元素
两数则固定一个元素+指针;
三数则固定两个元素+双指针;
四数则固定一个元素+三数和。
2.比较当前和与target的大小,移动指针。
因为提前将数组排序过,所以若当前和>target,则l--;
若当前和<target,则r++;
若当前和==target,则return或保存。
3.如果题目要求不重复,则去掉重复元素。
数组中元素可以重复,但是不能反回相同的答案,如[1,1,1,2],target=3;可以反回{[1,1,1] [1,2]},但不能多次返回[1,1,1] 以及 [1,2]。这时去掉首元素的重复元素:
while(i>0 && nums[i]==nums[i-1])i++;
//或者
if(i>0 && nums[i]==nums[i-1])continue;
同时如果找到了和==target的N元素,那么也需要去重,即:N个数据需要去重N次,固定的数用首元素去重的方法,双指针指向的数在找到和==target之后去重:
while(i>0 && nums[i]==nums[i-1])i++;
//或者
if(i>0 && nums[i]==nums[i-1])continue;
//以三数为例
while(l<r && nums[l]==nums[l+1])l++;
while(l<r && nums[r]==nums[r-1])r--;
例题:
两数之和这个题目,如果要求返回的是元素不是下标,那么可以用指针来解。
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
int n=nums.size();
sort(nums.begin(),nums.end());
int l=0,r=n-1;
while(l<r){
int sum=nums[l]+nums[r];
if(sum<target){
l++;
}
else if(sum>target){
r--;
}else return {l,r};
}
return {};
}
};
2.三数之和
三数之和且不包含重复元素,那么可以固定一个数,另外两个利用当前和与target的大小移动双指针确定,同时在首元素和中间元素都去重。这道题确定了target=0。
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
int l=0,r=0,sum=0;
vector<vector<int>> result;
sort(nums.begin(),nums.end());
for(int i=0;i<int(nums.size()-2);i++){
if(i>0 && nums[i]==nums[i-1] ) continue;
l=i+1;
r=nums.size()-1;
while(l<r && nums[r]>=0){
sum=nums[i]+nums[l]+nums[r];
if(sum==0 ){
result.push_back({nums[i],nums[l],nums[r]});
while(nums[r]==nums[r-1] && l<r) r--;
while(nums[l]==nums[l+1] && l<r)l++;
r--;
l++;
}else if(sum<0)l++;
else r--;
}
}
return result;
}
};
3.四数之和
四数之和就是固定两个数,再使用双指针,多了一层循环嵌套。
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
sort(nums.begin(),nums.end());
vector<vector<int>> res;
for(int i=0;i+3<nums.size();i++){
if(i>0 && nums[i]==nums[i-1])continue;
for(int j=i+1;j+2<nums.size();j++){
if(j>i+1 && nums[j]==nums[j-1])continue;
int l=j+1,r=nums.size()-1;
int ttarget = target - nums[i] - nums[j] ;
while(l<r){
int sum=nums[l]+nums[r];
if(sum>ttarget)r--;
else if(sum<ttarget)l++;
else {
res.push_back({nums[i],nums[j],nums[l],nums[r]});
while(l < r && nums[l] == nums[l+1]) l++;
while(l < r && nums[r] == nums[r-1]) r--;
l++;
r--;
}
}
}
}
return res;
}
};
注意小细节:
1.for(int i=0;i+3<nums.size();i++)而不是for(int i=0;i<nums.size()-3;i++),因为有可能出现nums中元素个数小于三,在这里浪费我近半个小时……
2.注意先去重,后再l++,r--;
3.去中间重复项是在找到和==target之和,而不是在之前,因为有可能出现一开头的[1,1,1]情况,元素可以重复,不能重复的是数组,在找到后去重也是保证了这个效果;
4.去重后还需要i++,l--,因为此时的i、j是重复的最后一项。
扩展:
4.最接近的三数之和
和==target很接近,只需要多一个步骤,先计算差,再计算下一步。
class Solution {
public:
int threeSumClosest(vector<int>& nums, int target) {
int n=nums.size(),res=nums[0]+nums[1]+nums[2];
sort(nums.begin(),nums.end());
for(int i=0;i<n-2;i++){
int l=i+1,r=n-1;
while(l<r){
int ans=nums[i]+nums[l]+nums[r];
if(abs(ans-target)<abs(res-target)){
res=ans;
}
if(ans>target){
r--;
}
else if(ans<target){
l++;
}
else return res;
}
}
return res;
}
};
核心还是固定一个数+双指针的移动,但是这里需要注意几个小细节,非常非常重要:
1)题目中没有说去掉重复元素,但是不去重直接计算会超时(不要问我怎么知道的),所以需要计算有几个重复的中间元素,相乘即可;
2)余数的可加性:a与b的和除以c的余数,等于a,b分别除以c的余数之和(或这个和除以c的余数)。所以可以先求余数再计算相加;
3)需要注意的是sum的位置,因为l和r指针是不断变化的,所以他一定是在while循环内;
4)注意此时while的条件,不再是l<r,而是A[l]<A[r],因为有元素相等的情况,并且元素相等情况特殊,需要单独讨论。
class Solution {
public:
int threeSumMulti(vector<int>& A, int target) {
int mod=1e9+7;
int ans=0;
sort(A.begin(),A.end());
int n=A.size();
for(int i=0;i<n-2;i++){
int l=i+1,r=n-1;
while(A[l]<A[r]){
int sum= A[l] + A[r]+A[i] ;
if(sum> target) r--;
else if(sum < target)l++;
else{
int tl = l;
int tr = r;
while (A[l] == A[tl])l++;
while (A[r] == A[tr])r--;
ans += (l - tl) * (tr - r);
ans %= mod;
}
}
if(A[l]==A[r] && A[l]+A[r]==target-A[i]){
int d = r - l + 1;
ans+=(d)*(d-1)/2;
ans%=mod;
}
}
return ans;
}
};
同理,如果五数之和就可以固定三个数,再用双指针。
细节决定成败,思路都对,测试也过了,就是提交不成功,这种感觉真的很难受,小菜鸡还是一步步慢慢来吧。