双指针_三数之和_C++

1.题目解析


leedcode题目链接:https://leetcode.cn/problems/3sum/

结合示例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] 。
注意,输出的顺序和三元组的顺序并不重要。

我们可以像这样选三个位置的数,让它们相加等于0:

在这里插入图片描述

其中[-1, 0, 1][0, 1, -1]是相同的三元组,因为其中的元素是相同的,元素的顺序不重要。


2.算法原理


2.1解法一:暴力解法


最原始的想法:先通过三层for循环,从左向右暴力枚举出所有可能的结果;然后对这些结果一一进行排序,目的是让像[-1, 0, 1][0, 1, -1]这样的结果都变成[-1, 0, 1],统一形式;最后将进过排序的结果放入set去重。

这样的想法可以进行一些优化:

1.先排序

先把数组进行一次排序,这样就不用对结果一一排序了,仅需一次大的排序即可。

2.再通过三层for循环,暴力枚举出所有可能结果

3.放入set去重

这样的暴力解法时间复杂度是O(n^3)


2.2解法二:双指针


1.先对数组排序

遇到有序数组,就想到双指针算法和二分法。其中,双指针算法可以在暴力解法的基础上,将时间复杂度直接降低一维。

2.固定一个数a

在这里插入图片描述

i指向第一个元素-4,固定这个数,然后用双指针在这个数后面的区间内搜索。

3.在这个数后面的区间内,利用双指针算法,快速找到两个数的和等于-a

这里可以参考我之前的博客,和为s的两个数。

4.处理细问题:

1)去重

1. left,right去重
在这里插入图片描述
找到一种结果之后,leftright指针要跳过重复元素:当left指向从左往右的第一个0,right指向从右往左的第一个4时nums[left] + nums[right] == -a,找到了一个结果。这时,left向右移动,right向左移动,还是有nums[left] + nums[right] == -a,但是这个结果和之前的结果重复,需要去重,所以直接让指针跳过这些重复的元素就可以了。

2. i去重
在这里插入图片描述
第一个-4处理完后,i向后移动一位,指向的元素还是-4,又要重复之前的搜索过程,得到的结果和之前的结果重复,需要去重。直接让i也跳过重复元素即可。

2)不漏记数

找到一种结果之后,指针不要停,缩小区间,继续寻找。

3)避免越界

这里不细讲,主要是通过leftright之间的关系来避免越界的,放到代码中具体分析。


3.代码演示


class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) 
    {
        vector<vector<int>> ret;
        // 1.排序
        sort(nums.begin(), nums.end());

        // 2.利用双指针解决问题
        int n = nums.size();
        for(int i = 0; i < n; i++)  
        {
            if(nums[i] > 0) break;          // 这里有一个小优化,一旦nums[i]>0了,就不用再向下找了
            int left = i + 1;
            int right = n - 1;
            int target = -nums[i];              // 定义目标值
            while(left < right)
            {
               int sum = nums[left] + nums[right];
               if(sum < target) left++;
               else if(sum > target) right--;
               else if(sum == target)
               {
                    ret.push_back({nums[i], nums[left], nums[right]});
                    left++, right--;            // 不漏

                    // left,right 的去重
                    while(left < right && nums[left] == nums[left - 1]) // left < right 的限制是防止越界
                        left++;
                    while(left < right && nums[right] == nums[right + 1])
                        right--;
               }
            }

            // 去重i
            while(i < n - 1 && nums[i] == nums[i + 1])  // i < n 的限制是防止越界
               i++;
            // 注意此处是i < n - 1,若写成n,测试用例[0, 0, 0, 0]无法通过
        }

        return ret;
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

-指短琴长-

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

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

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

打赏作者

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

抵扣说明:

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

余额充值