15.三数之和
思路
刚开始是准备用之前的方法哈希进行查找。但是题目中说到不能有重复的元素值。所以直接看视频讲解。
视频用到双指针法。先对整个数组进行排序,接着对数组每个元素遍历。
如果第一个元素就大于0,那么直接返回空。因为正数列表怎么加都不能为0。
left和right以及当前遍历的数值,如果三者相加为正,则说明要减少其和,right要向左移动;如果三者相加为负,则说明要增加其和,left要向右移动。
那么去重操作怎么做?
👇
Q1=》判断numbers[i]==numbers[i+1]
还是numbers[i]==numbers[i-1]
?
A1=》前者如果contine
,会把有重复的元素的结果集给筛掉。而后者则不会。(不理解,作为复习点,先记住。)
Q2=》遍历的退出条件是什么?
A2=》因为是不重复的,所以要用不重复的索引,那就是left<right
。遇到边界性问题,可以代入尝试,再选择退出条件。
接下来就是操作逻辑。left和right。去重操作逻辑:和当前遍历数值去重逻辑一致。判断条件是
对于right
,如果right>left && nums[right]==nums[right-1]
,则right--;
;
对于left
,若right>left && nums[left] == nums[left+1]
,则left++;
。
伪代码
对数组排序
//剪枝
判断首元素是否>0
如果大于0
则不满足题意,直接退出
遍历数组的每个元素
//去重
如果有重复元素
直接继续走
新建left指针
新建right指针
当left<right指针时
如果三者相加<0
left指针右移
如果三者相加>0
right指针左移
如果三者相加为0
将当前这三个元素作为数组添加到最后的结果集中
循环判断left指针是否和下一个元素重复,直到不重复为止
如果重复
left指针右移
循环判断right指针是否和下一个元素重复,直到不重复为止
如果重复
right指针左移
找到答案后,双指针同时收缩
返回结果集
实际代码
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> vtvt;
sort(nums.begin(), nums.end());
if (nums[0] > 0) {
return vtvt;
}
for (int i = 0; i < nums.size(); i++) {
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
int left = i + 1;
int right = nums.size() - 1;
while (left < right) {
if (nums[i] + nums[left] + nums[right] < 0) {
left++;
} else if (nums[i] + nums[left] + nums[right] > 0) {
right--;
} else {
vector<int> temp;
temp.push_back(nums[i]);
temp.push_back(nums[left]);
temp.push_back(nums[right]);
vtvt.push_back(temp);
while (left < right && nums[left] == nums[left + 1]) {
left++;
}
while (left < right && nums[right] == nums[right - 1]) {
right--;
}
// 找到答案时,双指针同时收缩
right--;
left++;
}
}
}
return vtvt;
}
18.四数之和
思路
和三数之和一致,不过因为多了一个数字,所以有两层循环,对应的剪枝和去重有两次。
剪枝:去除掉本来就不符合要求的元素。对于外层循环中的元素,如果符合target>0 && nums[k]>0 && nums[k]>target
则直接退出该循环。对于内层中的元素,因为到了内层,要把i
和k
看作整体,所以如果符合nums[k]+nums[i]>target && target > 0 && nums[i]>0
,则直接退出循环。
去重:对于重复的元素不再遍历,要求继续循环到不重复的元素。对于外层元素,则为k>0 && nums[k]==nums[k-1]
则继续循环到下一个不重复的元素。对于内层元素,则为i>k+1 && nums[i]==nums[i-1]
。
其它的则与三数之和一致。伪代码如下。
伪代码
对数组排序
外层循环
//剪枝
判断首元素是否>target && target>0 && 索引k>0
如果符合条件,则退出循环
//去重
判断元素和之前的元素是否重复
如果重复,则继续循环
去重
内层循环
//剪枝
判断内层循环元素+外层循环元素是否>target && target>0 && 内层索引是否大于外层索引
如果大于0
则不满足题意,直接退出
//去重
如果有重复元素
直接继续走
新建left指针
新建right指针
当left<right指针时
如果三者相加<0
left指针右移
如果三者相加>0
right指针左移
如果三者相加为0
将当前这三个元素作为数组添加到最后的结果集中
循环判断left指针是否和下一个元素重复,直到不重复为止
如果重复
left指针右移
循环判断right指针是否和下一个元素重复,直到不重复为止
如果重复
right指针左移
找到答案后,双指针同时收缩
返回结果集
实际代码
vector<vector<int>> fourSum(vector<int>& nums, int target) {
// target = long(target);
sort(nums.begin(), nums.end());
vector<vector<int>> vtvt;
for (int k = 0; k < nums.size(); k++) {
// 剪枝
if (k > 0 && target > 0 && nums[k] > 0 && nums[k] > target) {
break;
}
// 去重
if (k > 0 && nums[k] == nums[k - 1]) {
continue;
}
for (int i = k + 1; i < nums.size(); i++) {
// 剪枝
if (i > k + 1 && target > 0 && nums[i] + nums[k] > target &&
nums[i] + nums[k] >= 0) {
break;
}
// 去重
if (i > k + 1 && nums[i] == nums[i - 1]) {
continue;
}
int left = i + 1;
int right = nums.size() - 1;
while (left < right) {
if ((long)nums[k] + nums[i] + nums[left] + nums[right] >
target) {
right--;
} else if ((long)nums[k] + nums[i] + nums[left] +
nums[right] <
target) {
left++;
} else {
vector<int> vt;
vt.push_back(nums[k]);
vt.push_back(nums[i]);
vt.push_back(nums[left]);
vt.push_back(nums[right]);
vtvt.push_back(vt);
while (left < right && nums[left] == nums[left + 1]) {
left++;
}
while (left < right && nums[right] == nums[right - 1]) {
right--;
}
left++;
right--;
}
}
}
}
return vtvt;
}