一.2SUM问题
Given an array of integers, return indices of the two numbers such that they add up to a specific target.
You may assume that each input would have exactly one solution, and you may not use the same element twice.
Example:
Given nums = [2, 7, 11, 15], target = 9,
Because nums[0] + nums[1] = 2 + 7 = 9,
return [0, 1].
思路:
1.首先对数组按小到大排序,然后设定两个指针head、tail分别指向排序好的数组的首尾:
- 如果两个指针对应的元素和等于target,那么找到了
- 如果两个指针对应的元素和小于target,那么需要增加和的大小,则把head指针向后移动
- 如果两个指针对应的元素和大于target,那么需要减少和的大小,则把tail指针向前移动
- head赶上tail指针时,结束
- 由于本题中需要返回最后找到的数对的索引,因此,排序是我们不移动原来数组的元素,只是把元素的的索引放到一个新的数组,对这个新的索引数组排序
- 该算法复杂度为O(Nlog(N))
C++代码:
class Solution {
private:
static vector<int> *numbersCopy;
static bool cmp(int idx1, int idx2)
{
return (*numbersCopy)[idx1] < (*numbersCopy)[idx2];
}
public:
vector<int> twoSum(vector<int> &numbers, int target) {
numbersCopy = &numbers;
int n = numbers.size();
vector<int> res;
vector<int> idx(n);
for(int i = 0; i < n; i++)
idx[i] = i;
sort(idx.begin(), idx.end(), Solution::cmp);
int head = 0, tail = n-1;
while(head < tail)
{
int sum=numbers[idx[head]] + numbers[idx[tail]];
if(sum< target)
head++;
else if(sum> target)
tail--;
else //found
{
res.push_back(min(idx[head], idx[tail]));
res.push_back(max(idx[head], idx[tail]));
break;
}
}
return res;
}
};
vector<int> * Solution::numbersCopy = NULL;
2.将数组的数组映射到哈希表,key是元素的值,value是该值在数组中的索引。考虑到数组中元素有重复,我们使用STL中的unordered_map, 它可以允许重复的key存在。映射以后,对于数组中的某个元素num,我们只要在哈希表中查找num2 = target-num。需要注意的是在哈希表中找到了num2,并不一定代表找到了题目要求的两个数,比如对于数组2 7 11 15,target = 4,当num = 2时,num2 = target-num = 2,此时num2可以在哈希表中找到,但是num和num2指向的是同一个元素。因此当num2 = num时,在哈希表找到num2的同时,还需要保证哈希表中num2的个数>=2。
该算法时间复杂度为O(n).
C++代码:
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int,vector<int>> mark;
for(int i=0;i<nums.size();i++)
{
mark[nums[i]].push_back(i);
}
for(int i=0;i<nums.size();i++)
{
if(target-nums[i]==nums[i])
{
if(mark[nums[i]].size()>1)
{
vector<int> temp{i,mark[nums[i]][1]};
return temp;
}
}
else
{
if(mark.find(target-nums[i])!=mark.end())
{
vector<int> temp{i,mark[target-nums[i]][0]};
return temp;
}
}
}
}
};
二.3SUM问题
Given an array nums
of n integers, are there elements a, b, c in nums
such that a+ b + c = 0? Find all unique triplets in the array which gives the sum of zero.
Note:
The solution set must not contain duplicate triplets.
Example:
Given array nums = [-1, 0, 1, 2, -1, -4],
A solution set is:
[
[-1, 0, 1],
[-1, -1, 2]
]
思路:
1.先对数组排个序,排序之后,我们就可以对数组用两个指针分别从前后两端向中间扫描了,如果是 2Sum,我们找到两个指针之和为target就OK了,那 3Sum 类似,我们可以先固定一个数,然后找另外两个数之和为第一个数的相反数就可以了。3SUM的复杂度为O(N^2 )。(小tips:因为该数组已经从小到大排序,故而当出现nums[i]>0时即可执行break操作,当nums[i]==nums[i-1]时可以执行continue操作)。
S = {-1 0 1 2 -1 -4} 排序后: S = {-4 -1 -1 0 1 2} ↑ ↑ ↑ i j k → ← i每轮只走一步,j和k根据S[i]+S[j]+S[k]=ans和0的关系进行移动,且j只向后走(即S[j]只增大),k只向前走(即S[k]只减小) 如果ans>0说明S[k]过大,k向前移;如果ans<0说明S[j]过小,j向后移;ans==0即为所求。 至于如何取到所有解,看代码即可理解,不再赘述。
C++代码:
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> res;
if(nums.size()<3)
{
return res;
}
int i,left,right;
int sum=0;
sort(nums.begin(),nums.end());
for(i=0;i<nums.size()-2;i++)
{
if(nums[i]>0)
{
break;
}
if(i>0&&nums[i]==nums[i-1])
{
continue;
}
left=i+1;
right=nums.size()-1;
while(left<right)
{
sum=nums[i]+nums[left]+nums[right];
if(sum==0)
{
res.push_back({nums[i],nums[left],nums[right]});
left++;
while(left<nums.size()&&nums[left]==nums[left-1])
{
left++;
}
right--;
while(right>=0&&nums[right]==nums[right+1])
{
right--;
}
}
else if(sum>0)
{
right--;
}
else if(sum<0)
{
left++;
}
}
}
return res;
}
};
2. 先预处理一遍数组中两两相加的结果,然后再遍历每一个数nums[i]
,判断target-nums[i]
是否在预处理的那个和中,不过这种方法的复杂度也是O(N^2),主要是预处理的复杂度。
三.3SUM Cloest
Given an array nums
of n integers and an integer target
, find three integers in nums
such that the sum is closest to target
. Return the sum of the three integers. You may assume that each input would have exactly one solution.
Example:
Given array nums = [-1, 2, 1, -4], and target = 1. The sum that is closest to the target is 2. (-1 + 2 + 1 = 2).
思路:这道题让我们求最接近给定值的三数之和,在3SUM的基础上又增加了些许难度,那么这道题让我们返回这个最接近于给定值的值,即我们要保证当前三数和跟给定值之间的差的绝对值最小,所以我们需要定义一个变量diff用来记录差的绝对值,然后我们还是要先将数组排个序,然后开始遍历数组,思路跟那道三数之和很相似,都是先确定一个数,然后用两个指针left和right来滑动寻找另外两个数,每确定两个数,我们求出此三数之和,然后算和给定值的差的绝对值存在newDiff中,然后和diff比较并更新diff和结果closest即可。
C++代码:
class Solution {
public:
int threeSumClosest(vector<int>& nums, int target) {
if(nums.size()<3)
{
return 0;
}
int i,left,right;
int diff=INT_MAX;
int closest;
sort(nums.begin(),nums.end());
for(i=0;i<nums.size()-2;i++)
{
if(i>0&&nums[i]==nums[i-1])
{
continue;
}
left=i+1;
right=nums.size()-1;
while(left<right)
{
int sum=nums[i]+nums[left]+nums[right];
int newdiff=abs(sum-target);
if(newdiff==0)
{
return sum;
}
if(newdiff<diff)
{
diff=newdiff;
closest=sum;
}
if(sum<target)
{
left++;
}
else if(sum>target)
{
right--;
}
}
}
return closest;
}
};
四. 4SUM
Given an array nums
of n integers and an integer target
, are there elements a, b, c, and d in nums
such that a + b + c + d = target
? Find all unique quadruplets in the array which gives the sum of target
.
Note:
The solution set must not contain duplicate quadruplets.
Example:
Given array nums = [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]
]
思路:
1.先遍历第一个数,然后固定第一个数之后,转化为剩下元素的3SUM问题,其算法复杂度是稳定的O(n^3),最外层遍历一遍O(n^2),然后转为3SUM问题之后又是O(n^2)。这种方法相当于4SUM调用3SUM,然后3SUM再调用2SUM,这样函数调用有点多,不方便具体写出来的形式,可以写成最外层两个循环,即固定为两个数之后,再化为2SUM。
C++代码:
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>> res;
int n=nums.size();
if(n<4)
{
return res;
}
sort(nums.begin(),nums.end());
for(int i=0;i<n-3;i++)
{
if(i>0&&nums[i]==nums[i-1])
{
continue;
}
if(nums[i]+nums[i+1]+nums[i+2]+nums[i+3]>target)
{
break;
}
if(nums[i]+nums[n-1]+nums[n-2]+nums[n-3]<target)
{
continue;
}
for(int j=i+1;j<n-2;j++)
{
if(j>i+1&&nums[j]==nums[j-1])
{
continue;
}
if(nums[i]+nums[j]+nums[j+1]+nums[j+2]>target)
{
break;
}
if(nums[i]+nums[j]+nums[n-2]+nums[n-1]<target)
{
continue;
}
int left=j+1;
int right=n-1;
while(left<right)
{
int sum=nums[i]+nums[j]+nums[left]+nums[right];
if(sum==target)
{
res.push_back(vector<int>{nums[i],nums[j],nums[left],nums[right]});
left++;
while(left<right&&nums[left]==nums[left-1])
{
left++;
}
right--;
while(left<right&&nums[right]==nums[right+1])
{
right--;
}
}
else if(sum<target)
{
left++;
}
else if(sum>target)
{
right--;
}
}
}
}
return res;
}
};
2.先遍历两个数,求和,然后转化为剩下元素的2SUM问题。因为本质上我们是最外层两个循环之后,找是否有target-now的值,我们可以事先做好预处理,即空间换时间,先循环两次,找出两个数所有可能的和,存到map里(这里可以unordered_map)。这两等于是两个O(n^2)的时间复杂度相加和,所以最后时间复杂度为O(n^2); 但是此时需要有一个判重的问题,所以需要map中存如下数 mp[num[i]+num[j]].push_back(make_pair(i,j)); 然后再判重。
C++代码: