今天,带来哈希表和双指针相关算法的讲解。文中不足错漏之处望请斧正!
四数之和,去重版:找不重复的四元组
在数组nums中收集所有满足条件的四元组(不能重复),四元组条件:
根据《三数之和》,我们还是选择用双指针的方法来做。
循环不断拿取四元组, 满足条件就收割(不能收割重复的结果)。
怎么拿数能最快拿到结果(避免暴力遍历全部组合)?
排序,根据有序性动态控制双指针, 控制四数之和的大小, 保证自己逐渐接近结果, 而不是死板地暴力遍历所有可能性。
对a/b/c/d都要去重。
对ab的去重: ab是每次固定拿取的元素, 从这里去重, 是静态去重。
解释:假如本轮的a/b和上一轮的a/b相同, 并且上一轮的a和b可以和后面的某两个元素cd组成结果集, 那么我这一轮的ab也会找到上一轮已经组合过的cd再次组合, 成为重复的结果集
对cd的去重:cd是每次动态拿取的元素, 从这里去重, 是动态去重
解释:[0, -1, -1, -1, 1, 1, 1],这个例子只有一个结果集:[0, -1, 0]当我们收获了第一个结果集[0, -1, 1]后,left和right按理讲都往里面收缩一下。但不一定只是一下,如果只收缩一下,那就可能会重复收割相同的结果集。
剪枝有细节。
有些朋友可能会延续三数之和的思路,直接这样剪枝:
if(nums[i] > target) break;
但这样其实是默认了a、b、c、d都是正数。本题会有这样的情况:
nums = [-4, -1, 0, 0]
target = -5
再看看,[-4, -1, 0, 0]
明明是符合条件的结果集,但是nums[i](-4) > target(-5)
,直接break了。
所以,我们只有nums[i]
和target
都为正数才能剪枝。
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>> result;
// 排序
sort(nums.begin(), nums.end());
// 取固定的ab, 取动态变化的cd, 收割结果集
for (int i = 0; i < nums.size(); ++i) { // 固定取a
// 对a剪枝: 本题有负数, 只有二者都为正数才能这样剪枝
if (nums[i] > 0 && target > 0 && nums[i] > target) break;
if (i > 0 && nums[i] == nums[i - 1]) continue; // a的去重
for (int j = i + 1; j < nums.size(); ++j) { // 固定取b
// 对ab的和剪枝:
if (nums[i] + nums[j] > 0 && target > 0 && nums[i] + nums[j] > target) break;
if (j > i + 1 && nums[j] == nums[j - 1]) continue; // b的去重
int left = j + 1; // 移动取c d
int right = nums.size() - 1;
while (left < right) { // left 和 right 移动取 cd , 收割结果集
long long sum = (long long)nums[i] + nums[j] + nums[left] + nums[right]; // long long 防止相加溢出
if (sum > target) --right; // 四数之和大于target, 某个数需要变小, 让right--
else if (sum < target) ++left; // 四数之和小于target, 某个数需要变大, 让left++
else { // 收割结果集, 并跳过相同结果集(去重)
result.push_back({nums[i], nums[j], nums[left], nums[right]});
while (left < right && nums[left] == nums[left + 1]) ++left; // c的去重
while (left < right && nums[right] == nums[left - 1]) --right; // d的去重
// left 和 right 迭代, 继续移动, 收割结果集
++left;
--right;
}
}
}
}
return result;
}
};
步骤概括:
今天的分享就到这里了,感谢您能看到这里。
这里是培根的blog,期待与你共同进步!