个人主页:C++忠实粉丝
欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 C++忠实粉丝 原创双指针(7)_单调性_三数之和
收录于专栏【经典算法练习】
本专栏旨在分享学习C++的一点学习笔记,欢迎大家在评论区交流讨论💌
目录
1. 题目链接:
OJ链接----. - 力扣(LeetCode)
2.题目描述 :
给你一个整数数组 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] 。 注意,输出的顺序和三元组的顺序并不重要。
示例 2:
输入:nums = [0,1,1] 输出:[] 解释:唯一可能的三元组和不为 0 。
示例 3:
输入:nums = [0,0,0] 输出:[[0,0,0]] 解释:唯一可能的三元组和为 0 。
提示:
3 <= nums.length <= 3000
-105 <= nums[i] <= 105
3.解法 :
解法一(暴力枚举) :
算法思路 :
1. 对整个数组进行排序
2. 三层循环遍历数组
3. 使用set去重
代码展示 :
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
set<vector<int>> T;
sort(nums.begin(), nums.end());
int n = nums.size();
for(int i = 0; i < n - 2; i++)
for(int j = i + 1; j < n; j++)
for(int k = j + 1; k < n; k++)
{
if(nums[i] + nums[j] + nums[k] == 0)
{
T.insert({nums[i], nums[j], nums[k]});
}
}
vector<vector<int>> result(T.begin(), T.end());
return result;
}
};
结果分析 :
由于数据范围是3000,所以我们这个三层循环就算有了排序的优化,也没能通过(只差5个例子),
解法二(排序 + 双指针) :
这里大家可以先去看查找总价格为目标值的两个商品这道题,那道题与本题思路很像
--双指针(6)_单调性_查找总价格为目标值的两个商品-CSDN博客
算法思路 :
本题与两数之和类似,是非常经典的面试题
与两数之和稍微不同的是,题目中要求找到所有[不重复]的三元组.那我们可以利用在两数之和那里用的双指针思想,来对我们的暴力枚举做优化:
i. 先排序
ii. 然后固定一个数a:
iii. 在这个数后面的区间内,使用双指针算法快速找到两个数之和等于-a即可
但是要注意的是,这道题需要有[去重]操作~
i. 找到一个结果之后,left和right指针要[跳过重复]的元素
ii. 当使用完一次双指针算法之后,固定a也要[跳过重复]的元素
图解流程 :
开始我们就对数组进行排序,这样数组中的序列就是单调有序的,这样我们就可以使用双指针进行求解问题!!!
固定一个数,剩下的数构成二元组,然后在二元组里面利用双指针进行寻找
如果left + right < -dest
说明right左边所有数加left都小于dest
那么left就无需遍历,left++
如果left + right > -dest
说明left右边所有数加right都大于dest
那么right就无需遍历,right--
如果left + right == -dest
说明找到题目符合条件的数,插入到vector
left++,right--
注意:(去重问题)
这里也是left + right == -dest
说明找到题目符合条件的数,插入到vector
left++,right--
但是这里要判断nums[left - 1] == nums[left] 如果相等left直接++
nums[right] == nums[right - 1] 如果相等直接--
进行去重处理后left指向1, right也指向1
此时left + right < -dest lest++
left < right(不满足)循环结束
dest++
dest++的小优化:
当dest大于0时:
循环可以直接结束,因为我们的数组已经排序过
left和right也一定是大于0的
去重的边界问题
left 和right:
它们两个去重时,可能会访问到未定义的内存,需要进行条件束缚
left < right
dest:
它在去重时也可能会访问到未定义的内存,也需要进行条件束缚\
dest < nums.size()
代码展示 :
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> result;
sort(nums.begin(), nums.end());
int n = nums.size();
for(int dest = 0; dest < n; )
{
if(nums[dest] > 0) break;
int left = dest + 1, right = n - 1;
while(left < right)
{
if(nums[left] + nums[right] < -nums[dest]) left++;
else if(nums[left] + nums[right] > -nums[dest]) right--;
else
{
result.push_back({nums[dest], nums[left], nums[right]});
left++;
right--;
while(left < right && nums[left - 1] == nums[left]) left++;
while(left < right && nums[right] == nums[right + 1]) right--;
}
}
dest++;
while(dest < n && nums[dest] == nums[dest - 1]) dest++;
}
return result;
}
};
结果分析 :
顺利通过!!!
这道题很有价值,虽然难度是中等,但已经逼近困难了!这道的思路其实不难(其实就是之前双指针(6)中那题一样的思想),它难的地方就在于如何去重,在去重的过程中是否会注意到一些细节问题!
4.总结 :
这道题使用的是排序 + 双指针算法
1. 排序:
2. 固定一个数a:
3. 在该数后面的区间内,利用双指针算法快速找到两个的和等于-a即可
处理细节问题:
1. 去重
找到一种结果后,left和right指针要跳过重复元素
当使用完一次双指针算法之后,i也需要跳过重复元素
2. 不漏
找到一种结果后,不要停,缩小区间,继续寻找.