Leetcode15 三数之和(双指针)

3sum

题目

给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。

注意:答案中不可以包含重复的三元组。

例如, 给定数组 nums = [-1, 0, 1, 2, -1, -4],

满足要求的三元组集合为:
[
[-1, 0, 1],
[-1, -1, 2]
]

给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/3sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

第一时间想到暴力算法采用3重for循环,遍历所有3个数的可能组合,找到和为0的,并且丢掉重复的组合即可。但是这样复杂度过高 O ( n 3 ) O(n^3) O(n3),没有尝试,应该会超时。

观看了Krahets的题解,加上自己的理解写下这篇文章:先假定数组第一个数为3数和为0的其中一个,然后在剩下的数中枚举所有2数组合,找其他两个数,使得这3个数之和为0,如果找到记录下结果,枚举完后假定数组中下一个数为3数和中的一个,伪代码如下:

idx1 = 0//假定nums[idx1]能与nums[idx2],nums[idx3]相加和为0

whie (idx1< nums.size()-2) {

​ for (idx2 = idx1+1; idx2 < nums.size(); idx2++)

​ for (idx3 = idx2+1; idx3 < nums.size(); idx3++)

​ if (nums[idx1] + nums[idx2] + nums[idx3] == 0) 记录到结果中

​ idx1++;

}//这里没有考虑怎么去重

如果这里枚举所有2数组合采用两重for循环,那么上面的算法仍然是 O ( n 3 ) O(n^3) O(n3),但是可以利用双指针代替两重for循环,用双指针枚举时间复杂度为O(n)

具体的思路如下,要想使用双指针,先对nums数组进行排序(从小到大),时间复杂度为O(nlogn),设置3个指针,idx1,idx2,idx3,idx1初始指向数组的起始位置idx1=0,idx2=idx+1,idx3=nums.size()-1,现在目标变为在数组nums[idx2-idx3]中到两个数,使得nums[idx1]+nums[idx2]+nums[idx3] = 0。

设sum = nums[idx1] + nums[idx2] + nums[idx3],如果sum<0,那么只有将其中一个数增大才有可能使得sum=0,因为nums数组是有序的,只需要将idx2++即可,因为nums[idx2] <= nums[idx2+1],增加nums[idx2]的值才有可能使得sum从小于0转变为等于0。另一种情况,如果sum>0,这时只有让idx3–才有可能使sum从大于0转变为等于0。最后如果sum=0,则记录nums[idx1],nums[idx2],nums[idx3]到结果中,同时,idx2++,idx3–,此时必须让idx2和idx3跳过相同的元素直至一个新的元素,不然找到的结果会有重复的。当idx2==idx3时上述枚举过程结束,idx1++(此时同样需要跳过与nums[idx1]相同的元素,不然会有重复结果),idx2=idx1+1,idx3=nums.size()-1,开启下一轮枚举,上述算法时间复杂度为 O ( n 2 ) O(n^2) O(n2)

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> rst;
        if (nums.size() < 3) return rst;
        sort(nums.begin(), nums.end());
        int idx1 = 0, idx2, idx3;
        while (idx1 < nums.size()-2) {//固定nums[idx1],找另外两个数与之相加结果为0
            if (nums[idx1] > 0) break;//因为数组有序,nums[idx2],nums[idx3]>nums[idx1]此时不可能有3个数和为0
            idx2 = idx1+1;
            idx3 = nums.size()-1;
            while (idx2 < idx3) {//使用双指针查找nums[idx1] + nums[idx2] + nums[idx3] == 0 
                int sum = nums[idx1] + nums[idx2] + nums[idx3];
                if (sum > 0) {
                    idx3--;//要想和为0,只能移动idx3才能使3个数的和朝着和为0的目标靠近
                    //while (idx2 < idx3 && nums[idx3] == nums[idx3+1]) idx3--;//跳过所有相同的元素,这行非必须
                }
                if (sum < 0) {
                    idx2++;//要想和为0,只能移动idx2才能使3个数的和朝着和为0的目标靠近
                   //while (idx2 < idx3 && nums[idx2] == nums[idx2-1]) idx2++;//跳过所有相同的元素,这行非必须
                }
                if (sum == 0) {
                    rst.push_back({nums[idx1], nums[idx2], nums[idx3]});
                    idx2++;
                    while (idx2 < idx3 && nums[idx2] == nums[idx2-1]) idx2++;//跳过所有相同的元素,如果不跳过得到的结果中有相同的
                    idx3--;
                    while (idx2 < idx3 && nums[idx3] == nums[idx3+1]) idx3--;//跳过所有相同的元素,如果不跳过得到的结果中有相同的
                }
            }
            idx1++;
            while (idx1 < nums.size()-2 && nums[idx1] == nums[idx1-1]) idx1++;//跳过所有相同的元素,如果不跳过得到的结果中有相同的
        }
        return rst;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值