力扣15 - 三数之和【奇妙的双指针】

Hello大家好,又做到一题比较有挑战性的题目,一种详细而又巧妙的解法送给大家💝

一、原题描述

原题传送门
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。

注意:答案中不可以包含重复的三元组。(很烦😠)

示例 1:

输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]

示例 2:

输入:nums = [0,1,1]
输出:[]

示例 3:

输入:nums = [0,0,0]
输出:[[0,0,0]]

二、思路分析

1、题型引入

  • 之前有做过一道题叫做力扣1.两数之和,是用unordered_map分别记录数组元素的值和所属下标,然后去查找是否有符合两数之和等于target的元素值,若是有,则将其下标返回,若不是,则将其放入map中做匹配用

既然说到这份上了,就顺便给出代码吧🌾

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        unordered_map<int,int> map;
        for(int i = 0;i < nums.size(); ++i)
        {
            auto iter = map.find(target - nums[i]);
            if(iter != map.end())
                return {iter->second,i};
            else
                map.insert(pair<int,int>(nums[i],i));
        }
        return {};
    }
};
  • 然后对于这道三数之和,可谓大相径庭,不仅是需要三个数,而且返回的不是下标,是那个所属元素,不过最复杂的还是答案中不可以包含重复的三元组这句话,比较烦人,因为这就需要考虑三个数去重后的情况
  • 这题其实也可以利用哈希法来解,但是去重的部分过于复杂而且效率又不是很高,便不做细解,给出C++代码给大家看一下
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> result;
        sort(nums.begin(), nums.end());
        // 找出a + b + c = 0
        // a = nums[i], b = nums[j], c = -(a + b)
        for (int i = 0; i < nums.size(); i++) {
            // 排序之后如果第一个元素已经大于零,那么不可能凑成三元组
            if (nums[i] > 0) {
                break;
            }
            if (i > 0 && nums[i] == nums[i - 1]) { //三元组元素a去重
                continue;
            }
            unordered_set<int> set;
            for (int j = i + 1; j < nums.size(); j++) {
                if (j > i + 2
                        && nums[j] == nums[j-1]
                        && nums[j-1] == nums[j-2]) { // 三元组元素b去重
                    continue;
                }
                int c = 0 - (nums[i] + nums[j]);
                if (set.find(c) != set.end()) {
                    result.push_back({nums[i], nums[j], c});
                    set.erase(c);// 三元组元素c去重
                } else {
                    set.insert(nums[j]);
                }
            }
        }
        return result;
    }
};

2、对比分析

  • 最后回归我们的正题,就是用双指针的方法来解这道题,我觉得这是比较优的而且也比较巧妙的一种方法,如果您还能想出比此方法还优的解法记得私信我哦,讲一下整体的这个思路,主要就是先对给到nums数组做一个排序,这样才可以对它进行一个前后的移位遍历,两数之和是要返回下标的话去排序的话顺序就都乱了,但三数之和返回的不是下标,只是数字,因此可以将其重新打乱顺序,这里的排序也是这题的一个重要环节,对后面的所有代码起着关键作用
  • 将三个数设置为a,b,c,题目要求是a + b + c = 0,所以我们要去获取a,b,c三个数,这里的a,可以用起始下标i获取,对于b,c,我这里是设置了一个前后指针left和right,去前后不断地搜寻符合条件的这两个数
  • 这题的双指针很灵巧又易于理解,但去重是一个关键,因为题目讲了需要的是一个不重复的三元组,也就是你result中有{1,2,3}了,就不可以再包含重复的与这三个数一样的一个三元组了,关于具体的操作和代码,且听我慢慢道来
    在这里插入图片描述

三、代码详解

1、分步骤解析

  • 首先,你需要有一个最终的结果集去收集并返回,而且前面我们说过,在最前面要对所给数组排个序,这步操作是后面代码的最重要基础,不可忽略🔑,因为只有排了序,后面的前后指针才可以进行移位替补操作
 vector<vector<int>> result;
 sort(nums.begin(),nums.end());      //返回不是下标,因而可以排序
 //a + b + c = 0
 //nums[i] = a   nums[left] = b  nums[right] = c
  • 然后就是去遍历这个数组
 for(int i = 0;i < nums.size(); ++i)
  • 接下来首先应该去考虑的就是特殊一点的情况,因为是排过序的数组,前面一定是最小的,如果第一个数a > 0,则相加不可能为0,因此一旦碰到第一个数就为0的情况,直接return result即可。
  • 接着我们就要去考虑对第一个数a去重,有些小伙伴可能直接写nums[i] == nums[i + 1],这就不对了,你拿一个数和它后一个数去做比较,但a的后一个数接的是left指针,这相当于错位混乱了,更像是去判断一个结果集中是否有重复元素,正确的写法应该是nums[i - 1]才对,让nums[i]去和它前一个数做比较,相等和不相等都不会影响后面的left指针
  • 但还有一点也要考虑到,就是我们示例1中给到的这种,[-1,-1,2],你想要是判断到-1 == -1,直接continue了,那不是缺失了这一种结果了吗,所以更加严谨的写法应该多加一个判断,那就是i > 0,如果所属下标值的元素满足这种情况,则强制进入下一循环,放弃这一循环的遍历
 if(nums[i] > 0)     //因为是排过序的数组,前面一定是最小的,如果a > 0,则相加不可能为0
     return result;
 //对a去重
 //要考虑到[-1,-1,2],若不写i > 0,则会略过这一种情况
 //            if(nums[i] == nums[i - 1])   continue;
 if(i > 0 && nums[i] == nums[i - 1])   continue;

接下去轮到本题的重头戏——双指针登场了,准备头脑风暴🌊

  • 前面给出过图示,left指针就位于下标i之后,right指针位于本数组的最后一位
  • 然后就是两个指针的判断和交替移动,直到left == right为止,有些小伙伴很疑惑为什么不是left <= right,因为left和right不能相等,而且所求集合为三元组,若left与 right相等,则指向同一元素,只有两个数便不符合题目要求了,因而将此作为循环的退出条件
 int left = i + 1;                   //左指针为i + 1
 int right = nums.size() - 1;        //右指针为nums.size() - 1
 while(left < right)
 //left和right不能相等,因为所求集合为三元组,若left == right,则指向同一元素
  • 这是就又有人心急着想要对b,c进行去重了,但是在循环一开始就去重的话是不妥的,也是会丢掉一种[0,0,0]的情况,因为我们在后面对符合要求的三元组要进行取值操作,如果在循环开始就判断left和right是否相同,那两个内嵌while循环一直移动判定,直到left和right指向同一个0为止,就直接会判定此结果不符合,便跳出外层while循环了,所以我们不能在此处对b,c进行去重
     //此处不可对左右指针去重,否则会漏掉[0,0,0]这一种情况
//       if(left < right && nums[left] == nums[left + 1])  left++;
//       if(left < right && nums[right] == nums[right - 1])  right--;
  • 最后就是内部循环的操作,前面说过,因为排了序,数据呈现一个升序,操作起来就会很方便,我们直接对nums[i] + nums[left] + nums[right]是否 == 0进行一个判断,
    ①如果三者之和 < 0,则说明和不够大,这时就将左指针后移,取得更大数字
    ②如果三者之和 > 0,则说明和太大,这时就将右指针前移,取得更小数字
    ③如果恰好 = 0,那么则说明刚好取到一组之和为0的三个数,就定义一个小结果集三个数放入其中并将小结果集push_back()进大结果集中
 //因排过序,为升序
 if(nums[i] + nums[left] + nums[right] < 0)  
     left++;     //左指针后移,取得更大数字
 else if(nums[i] + nums[left] + nums[right] > 0)
     right--;    //右指针前移,取得更小数字

 else{       //表示刚好取到一组之和为0的三个数
     result.push_back(vector<int>{nums[i],nums[left],nums[right]});
     //在获取一个三元组后,前后指针继续移动去重b,c
     //需使用while,直到符合正确条件为止,因为b,c可能连着相等
     while(left < right && nums[left] == nums[left + 1])  left++;
     while(left < right && nums[right] == nums[right - 1])  right--;

     //找到正确答案后,双指针同时收缩
     left++;
     right--;
  • 在获取一个三元组后,我们便可以操纵前后指针继续移动去重b,c,因为此时已经找到了一组符合条件的值,在此处去重时,比较重要的一点是要使用while循环一直改变left和right的指向,不然找到一个相同就跳出进行下一外循环了,这就是我第一次提交后的反馈,很明显,这里的b,c还没有去重,右指针在指向第二个1之后便直接退出了,因此多收集了一组结果
  • 最后,在找到正确答案后,双指针同时收缩即可,直到left == right为止,便跳出外层while循环
    请添加图片描述

2、指针数据变化表

接着给出一个完整的收集数据的过程展示,就不一张张图片显示了,大家可以对照着开头的初始指针指向图和下一环节的动画展示自己在画图软件里推敲一下🔍会加深对这道题的理解

在这里插入图片描述

3、动画展示

前面有说到一组[-1,-1,2]的情况,用动画呈现(微信手机端看不到)

4、整体代码展示

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> result;
        sort(nums.begin(),nums.end());      //返回不是下标,因而可以排序

        //a + b + c = 0
        //nums[i] = a   nums[left] = b  nums[right] = c
        for(int i = 0;i < nums.size(); ++i)
        {
            if(nums[i] > 0)     //因为是排过序的数组,前面一定是最小的,如果a > 0,则相加不可能为0
                return result;
            //对a去重
            //要考虑到[-1,-1,2],若不写i > 0,则会略过这一种情况
//            if(nums[i] == nums[i - 1])   continue;
            if(i > 0 && nums[i] == nums[i - 1])   continue;

            int left = i + 1;                   //左指针为i + 1
            int right = nums.size() - 1;        //右指针为nums.size() - 1
            while(left < right)                  
            {
        //left和right不能相等,因为所求集合为三元组,若left == right,则指向统一元素

                //此处不可对左右指针去重,否则会漏掉[0,0,0]这一种情况
//                if(left < right && nums[left] == nums[left + 1])  left++;
//                if(left < right && nums[right] == nums[right - 1])  right--;

                //因排过序,为升序
                if(nums[i] + nums[left] + nums[right] < 0)  
                    left++;     //左指针后移,取得更大数字
                else if(nums[i] + nums[left] + nums[right] > 0)
                    right--;    //右指针前移,取得更小数字

                else{       //表示刚好取到一组之和为0的三个数
                    result.push_back(vector<int>{nums[i],nums[left],nums[right]});
                    //在获取一个三元组后,前后指针继续移动去重b,c
                    //需使用while,直到符合正确条件为止,因为b,c可能连着相等
                    while(left < right && nums[left] == nums[left + 1])  left++;
                    while(left < right && nums[right] == nums[right - 1])  right--;

                    //找到正确答案后,双指针同时收缩
                    left++;
                    right--;
                }
            }
        }
        return result;      //返回最后的可能结果
    }
};

四、回顾总结

本题的讲解到这里就结束了,看完了这种双指针的方法,您是否又多了一种解题的方法和思路,其实有很多题目都可以用双指针这个算法来实现,也会显得比较精确而巧妙,如果您对讲解的哪处有所疑问,可以于评论区或者私信我,感谢您对本文的观看🌹

以下是相关的题型,看完本题可以去继续去练练手

1.两数之和
18. 四数之和

刷题的心路历程

  • 大一这一学年没有刷过题,也不知道去哪里刷题,直到这个暑假,才开始慢慢地在LeetCode上刷题,也渐渐地提升了自己的算法和编程能力。回想到自己初学C语言的时候,初次接触指针这个东西,完全就明白不了,指针指向内存中某块地址是何意思,慢慢地通过看b站上面各种关于指针的详细讲说,以及鹏哥对于指针的教授,还有在C站中对各种文章的浏览与学习,才指针地了解了指针,明白了指针在C/C++中的重要性和地位,直到现今,我不仅可以轻松熟练使用指针,而且可以使用双指针来解题目,我感到非常开心与自豪,觉得我这一路走来以及这一路的学习并没有白费,而且我觉得自己对C/C++这门语言了解得还不是很深刻,希望在接下来的学习中,可以更加深入地学习与了解更多新的知识
  • 21
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 38
    评论
以下是力扣LeetCode)上的经典问题:两数之和(Two Sum)的 C 语言解法。 ```c #include <stdio.h> #include <stdlib.h> int* twoSum(int* nums, int numsSize, int target, int* returnSize) { int i, j; int* result = (int*)malloc(sizeof(int) * 2); *returnSize = 2; for (i = 0; i < numsSize - 1; i++) { for (j = i + 1; j < numsSize; j++) { if (nums[i] + nums[j] == target) { result[0] = i; result[1] = j; return result; } } } return NULL; } int main() { int nums[] = {2, 7, 11, 15}; int target = 9; int returnSize; int* result = twoSum(nums, sizeof(nums) / sizeof(nums[0]), target, &returnSize); if (result != NULL) { printf("[%d, %d]\n", result[0], result[1]); free(result); } return 0; } ``` 这个程序首先定义了一个函数 `twoSum()`,它接受一个整数数组 `nums`,数组的长度 `numsSize`,目标值 `target` 和一个指向整数变量的指针 `returnSize`。这个函数的目的是在给定数组中找到两个数,它们的和等于目标值,并返回这两个数的下标。 函数内部使用了两个嵌套的循环来枚举数组中的所有可能的组合,并检查它们的和是否等于目标值。如果找到了符合条件的组合,就把它们的下标存储在一个动态分配的整数数组中,并返回这个数组的指针。如果没有找到符合条件的组合,就返回空指针。 在 `main()` 函数中,我们定义了一个测试用例,即整数数组 `{2, 7, 11, 15}` 和目标值 `9`。然后我们调用 `twoSum()` 函数,传入这个测试用例,并打印出返回的结果。 值得注意的是,我们在 `twoSum()` 函数内部动态分配了一个整数数组 `result`,然后在 `main()` 函数中使用 `free()` 函数释放了它的内存,以避免内存泄漏。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 38
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

烽起黎明

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值