今天依旧是哈希表相关。不过虽说是哈希表章节但采用的均为双指针解法,今天两道题结合在一起进行分析。
15.三数之和
题目:给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请
你返回所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例 1:
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> res;
sort(nums.begin(),nums.end());
for(int i=0;i<nums.size();i++){
if(nums[0] > 0) return res;//如果排序后最小的数都大于0,那么肯定不存在三元组满足题意
int left = i+1;
int right = nums.size() - 1;
//去重i
/*
错误的去重,这样会错过[-1,-1,2]这样的情况,即第一个元素与第二个元素相同,且三元组能满足题意。
if(nums[i] == nums[i+1]){
continue;
}
*/
if(i > 0 && nums[i] == nums[i - 1]){
continue;
}
while(left < right){
if(nums[i] + nums[left] + nums[right] > 0) right--;
else if(nums[i] + nums[left] + nums[right] < 0) left++;
else{
res.push_back(vector<int>{nums[i],nums[left],nums[right]});
//j,k的去重一定要找到一种情况并存储之后
while(left <right && nums[right] == nums[right-1]) right--;
while(left < right && nums[left] == nums[left + 1]) left++;
//找到一组解后,双指针要同时移动一个元素
right--;
left++;
}
}
}
return res;
}
};
18.四数之和
给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):
0 <= a, b, c, d < n
a、b、c 和 d 互不相同
nums[a] + nums[b] + nums[c] + nums[d] == target
你可以按 任意顺序 返回答案 。
示例 1:
输入:nums = [1,0,-1,0,-2,2], target = 0
输出:[[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>> res;
sort(nums.begin(),nums.end());
for(int a = 0;a<nums.size();a++){
//对a剪枝,如果排序后最小值都比target大而且是大于0的,那么肯定不存在四元组满足题意
if(nums[a] > target && nums[a] >= 0){
break;
}
//对a去重
if(a > 0 && nums[a] == nums[a-1]) continue;
for(int b = a+1;b<nums.size();b++){
//对b剪枝,如果排序后最小的两个值相加大于target且和大于0,那么肯定不存在四元组满足题意
if(nums[a] + nums[b] >= 0 && nums[a] + nums[b] > target){
break;
}
//对b去重
if(b > a+1 && nums[b] == nums[b-1]) continue;
int left = b+1;
int right = nums.size() - 1;
while(left < right){
if((long)nums[a] + nums[b] + nums[left] + nums[right] > target) right--;
else if((long)nums[a] + nums[b] + nums[left] + nums[right] < target) left++;
else{
res.push_back(vector<int>{nums[a],nums[b],nums[left],nums[right]});
//取到结果后对c,d去重
while(left < right && nums[right] == nums[right-1]) right--;
while(left < right && nums[left] == nums[left + 1]) left++;
//找到一次结果后双指针同时移动一个元素
right--;
left++;
}
}
}
}
return res;
}
};
思路:
1.这两道题相比起四数相加II,变化的点在于从四个数组中各提取一个数变成了从一个数组中提取三个数,且要求答案中不包含重复的n元组。
2.四数相加II其实本质就是两数相加,只不过将两个数组各提取的一个元素的和作为一个数,采用哈希表做法即可快速解决。
3.但这两道题因为要剪枝与去重,采用哈希表进行操作较为复杂,因此采用另一种双指针的方法。
4.核心在于如何剪枝与去重?首先弄清楚剪枝与去重在本题中的情况。去重即为重复的情况(因为题目要求不包含重复n元组);剪枝即为直接排除掉不满足题意的情况。
5.用三元组举例子。双指针法首先将数组排序,然后用一个临时变量for循环遍历整个数组,同时定义一个left指针为i+1,right指针指向数组尾部;对i进行剪枝与去重(见代码段注释)。
计算nums[i] + nums[left] + nums[right],如果大于target说明偏大了,让right左移;如果小于target说明偏小了,让left右移;如果相等,说明找到了一组满足要求的n元组,将其加入结果数组中。注意,left和right的去重一定要在找出一组结果后再进行。
6.同样的解法适用于任何n元组,四元组比起三元组多一层for循环遍历,五元组、六元组以此类推。