每日一题系列(day 18)
前言:
🌈 🌈 🌈 🌈 🌈 🌈 🌈 🌈 🌈 🌈 🌈 🌈 🌈
🔎🔎如果说代码有灵魂,那么它的灵魂一定是👉👉算法👈👈,因此,想要写出💚优美的程序💚,核心算法是必不可少的,少年,你渴望力量吗😆😆,想掌握程序的灵魂吗❓❗️那么就必须踏上这样一条漫长的道路🏇🏇,我们要做的,就是斩妖除魔💥💥,打怪升级!💪💪当然切记不可😈走火入魔😈,每日打怪,拾取经验,终能成圣🙏🙏!开启我们今天的斩妖之旅吧!✈️✈️
题目:
给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a ,b ,c ,使得 a + b + c = 0 ?请找出所有和为 0 且 不重复 的三元组。
示例:
提示:
- 0 <= nums.length <= 3000
- -105 <= nums[i] <= 105
解法一:暴力枚举
首先分析题目,题目让我们返回一个或多个不同的三元组,可以不用考虑顺序,但是不能有两组三元组都是相同元素,那么首先我们考虑暴力枚举策略:
先将数组排个序,以便于更好的查看重复的三元组,之后再使用三层for循环将所有情况都枚举出来,判断是否是符合条件的三元组,如果是,组成一维数组尾插进二维数组res中,当遍历完了之后使用set进行去重即可。
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
if(nums.size() < 3)
return {};
sort(nums.begin(), nums.end());
vector<vector<int>> res;
int n = nums.size();
for(int i = 0 ; i < n - 2 ; i++)
{
for(int j = i + 1 ; j < n - 1 ; j++)
{
for(int k = j + 1 ; k < n ; k++)
{
if((nums[i] + nums[j] + nums[k]) == 0)
{
res.push_back(vector<int>{nums[i], nums[j], nums[k]});
}
}
}
}
set<vector<int>> uniq(res.begin(), res.end());
res.assign(uniq.begin(), uniq.end());
return res;
}
};
测试用例可以通过,但是提交却发现还有三个测试用例跑不过,这时我们就要另想他法了。
解法二:双指针解法
我们还可以在暴力的基础上使用双指针解法,我们首先定住一个值,然后使用双指针来标记另外两个值,定住的值不动,双指针遍历剩下的数组元素,找出所有正确的三元组,尾插进二维数组里,数组遍历完,定值再向右遍历,双指针重置。这样循环上述步骤,就可以得到所有三元组,具体的步骤:
1、数组元素个数不满三个的直接返回空数组。创建二维数组用来存放三元组,为了更方便识别相同的三元组,我们将数组的值排个序。
2、固定一个值,在固定值后一个位置设置左指针,最后一个元素下标设为右指针,将固定值使用变量target记录。
3、而题目要求我们三元组的和为0,也就是要我们固定值和左右指针指向的值和为0,又数组是升序数组,如果target的值大于0了,则左右指针的值肯定也会>0,所以固定值一定要保证小于0。
4、固定值选取后,开始用双指针对数组剩下的元素进行遍历了,使用变量sum记录下左右指针指向元素的和,用来与target的值进行比较。
5、如果sum的值要大于target,说明右指针大了,数组为升序数组,则右指针向左移一位,再次进行比较。而如果sum的值小于target,说明左指针小了,将左指针右移一位,再次进行比较。
6、如果sum的值等于target的值,那么表示此时的左右指针加上固定值,就是一个符合规矩的三元组,将此三元组合并为一个一维数组尾插进二维数组ans数组中。
7、题目的测试用例说明了不能有重复的三元组出现,那么我们就需要进行去重操作,那么什么情况下才会有重复元素出现呢?
【1】当固定值不变的时候,左指针的的下一个位置的值和上一个位置的值相等,再次进行比较,就是一个新的三元组,但是这个三元组的值和上一个三元组的值就重复了,不仅左指针如此,右指针也是如此,所以需要将左右指针指向和上一个值不同的值。
【2】当固定值改变的时候,当数组遍历完,固定值需要更新,如果新固定值的值和原来的值相同,那么这组数据便利出来的三元组就和上组数据便利出来的三元组重复了,所以固定值也需要指向新的不重复的值。
代码演示:
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
if(nums.size() < 3)//不满一个三元组
return {};
vector<vector<int>> ans;//二维数组
sort(nums.begin(), nums.end());//将数据进行排序
int n = nums.size();
for(int i = 0 ; i < n;)//固定一个数,然后使用双指针进行三元组比较
{
if(nums[i] > 0) break;//target的值若>0,而数组又是升序,则后面的数一定也是大于0的
int left = i + 1, right = n - 1, target = -nums[i];//左指针为i的下一个位置,右指针为最后一个位置,target为i位处与左右指针相加进行比较的元素
while(left < right)
{
int sum = nums[left] + nums[right];//记录左右指针相加的值
if(sum > target) right --;//用sum和target值进行比较,>0时说明右指针太大了
else if(sum < target) left ++;//<0,说明左指针太小了
else//等于0,是符合条件的三元组
{
ans.push_back({nums[left], nums[right], nums[i]});//将符合条件的三元组尾插进数组
left ++;//左右指针都移动一位
right --;
//去重操作
while(left < right && nums[left] == nums[left - 1]) left ++;//如果左指针下一个值和前一个值相等,则得到的是重复的三元组,为了去重,将左指针向后移动,且要保证左指针小于右指针防止越界
while(left < right && nums[right] == nums[right + 1]) right--;//同理,右指针的值也要保证和上一个右指针的值不同,且要保证右指针不越界
}
}
++i;//这时上一个i的情况已经全部枚举完了,i指向数组的下一个位置
while(i < n && nums[i] == nums[i - 1]) i++;//同理,i的下一个位置和上一个位置传的值如果是相同的,那么就会发生重复的三元组,为了去重,将i自增至于上一个i的值不同的位置
}
return ans;//返回二维数组
}
};
这个双指针解法和我们前面写过的类似,可以先看看这题再来做我们说的这道题,思路就会很清晰了:查找总价格为目标值的两个商品