- Two Sum问题
先来看Two Sum问题
有许多种做法,我第一个想到的是用哈希表来做,在C++里也就是unordered_map,用它来存贮nums中的元素以及对应的下标。然后在遇到新的元素的时候进行查找时候存在index[ target-nums[i]]这个键,如果存在就找到了需要的元素。
vector<int> twoSum(vector<int>& nums, int target)
{
vector<int> ans;
int len=nums.size();
unordered_map<int,int> index;
for(int i=0;i<len;i++)
{
if(index.find(target-nums[i])!=index.end())
{
ans.push_back(i);
ans.push_back(index[target-nums[i]]);
}
index[nums[i]]=i;
}
return ans;
}
后来在网上找到了更好的双指针解法,具体来说就是用两个指针low和high分别指向数组的头部和尾部,看指向的值的加和会不会等于target,如果比target小,则low++,如果比target大,则high–。要做到这一点,我们就得给数组排好序。从而保证low和high指向的始终是[low,high]区间的最小值和最大值。
代码如下:
vector<int> twoSum(vector<int>& nums, int target) {
// 先对数组排序
sort(nums.begin(), nums.end());
// 左右指针
int lo = 0, hi = nums.size() - 1;
while (lo < hi) {
int sum = nums[lo] + nums[hi];
// 根据 sum 和 target 的比较,移动左右指针
if (sum < target) {
lo++;
} else if (sum > target) {
hi--;
} else if (sum == target) {
return {nums[lo], nums[hi]};
}
}
return {};
}
- Three Sum问题
在这里ThreeSum问题要求找出a+b+c=0条件成立的a,b,c。我们稍微转化一下思路,可以看成找出a+b=-c成立的a,b,c值。那么问题就很明朗了,我们只需要穷举c,然后调用twosum函数就可以解决。
先看代码
vector<vector<int>> threeSum(vector<int>& nums)
{
// 数组首先排个序
sort(nums.begin(), nums.end());
int n = nums.size();
vector<vector<int>> res;
// 穷举 threeSum 的第一个数
for (int i = 0; i < n; i++)
{
// 对 target - nums[i] 计算 twoSum
vector<vector<int>>
tuples = twoSumTarget(nums, i + 1, - nums[i]);
// 如果存在满足条件的二元组,再加上 nums[i] 就是结果三元组
for (vector<int>& tuple : tuples)
{
tuple.push_back(nums[i]);
res.push_back(tuple);
}
// 跳过第一个数字重复的情况,否则会出现重复结果
while (i < n - 1 && nums[i] == nums[i + 1]) i++;
}
return res;
}
vector<vector<int>> twoSumTarget(vector<int>& nums, int start, int target)
{
// 左指针改为从 start 开始,其他不变
//为什么左边指针改为从start(也就是i+1)开始?
//因为start之前的元素都是在穷举ThreeSum的第一个数的时候穷举过的,如果将这些元素再次加入作为第二个或第三个数字,那么就会造成重复。
int lo = start;
int hi = nums.size() - 1;
vector<vector<int>> res;
while (lo < hi)
{
int sum = nums[lo] + nums[hi];
int left = nums[lo], right = nums[hi];
if (sum < target)
{
//这里while是防止重复的值出现
while (lo < hi && nums[lo] == left) lo++;
}
else if (sum > target)
{
//这里while是防止重复的值出现
while (lo < hi && nums[hi] == right) hi--;
}
else
{
res.push_back({left, right});
//这里while是防止重复的值出现
while (lo < hi && nums[lo] == left) lo++;
while (lo < hi && nums[hi] == right) hi--;
}
}
return res;
}
我们这里需要注意的是解可能会重复出现,在求解的时候需要十分小心地考虑去重。
对于twosum函数来说,如果lo和hi指向的值之前已经出现过了,就需要跳过,否则若匹配成功就会给加入到结果集合res中。对于threesum函数来说,我们穷举nums[i]的时候也需要注意跳过后边重复出现的值。
同时,因为在穷举ThreeSum的第一个数的时候穷举过的nums[i],如果将这些元素再次加入twosum中作为第二个或第三个数字,那么就会造成重复。因此low指针必须从threesum穷举过的元素下标的下一个开始。
- Four Sum问题
学会了ThreeSum问题,FourSum问题就很好解决了,其实就是之前的问题的套娃。代码如下:
vector<vector<int>> fourSum(vector<int>& nums, int target)
{
int n=nums.size();
sort(nums.begin(),nums.end());
vector<vector<int>> res;
for(int i=0;i<n;i++)
{
vector<vector<int>> tirples=
threeSum(nums,i+1,target-nums[i]);
for(vector<int>& tirple : tirples)
{
tirple.push_back(nums[i]);
res.push_back(tirple);
}
while(i<n-1 && nums[i]==nums[i+1]) i++;
}
return res;
}
vector<vector<int>> threeSum(vector<int>& nums, int start, int target)
{
int n = nums.size();
vector<vector<int>> res;
// 穷举 threeSum 的第一个数
for (int i = start; i < n; i++)
{
// 对 target - nums[i] 计算 twoSum
vector<vector<int>>
tuples = twoSumTarget(nums, i + 1, target- nums[i]);
// 如果存在满足条件的二元组,再加上 nums[i] 就是结果三元组
for (vector<int>& tuple : tuples)
{
tuple.push_back(nums[i]);
res.push_back(tuple);
}
// 跳过第一个数字重复的情况,否则会出现重复结果
while (i < n - 1 && nums[i] == nums[i + 1]) i++;
}
return res;
}
vector<vector<int>> twoSumTarget(vector<int>& nums, int start, int target)
{
// 左指针改为从 start 开始,其他不变
//为什么左边指针改为从start(也就是i+1)开始?
//因为start之前的元素都是在穷举ThreeSum的第一个数的时候穷举过的,如果将这些元素再次加入作为第二个或第三个数字,那么就会造成重复。
int lo = start;
int hi = nums.size() - 1;
vector<vector<int>> res;
while (lo < hi)
{
int sum = nums[lo] + nums[hi];
int left = nums[lo], right = nums[hi];
if (sum < target)
{
while (lo < hi && nums[lo] == left) lo++;
}
else if (sum > target)
{
while (lo < hi && nums[hi] == right) hi--;
}
else
{
res.push_back({left, right});
while (lo < hi && nums[lo] == left) lo++;
while (lo < hi && nums[hi] == right) hi--;
}
}
return res;
}