LeetCode刷题系列:15

题目描述

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

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

示例:

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

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

解题思路

  • 简单的做法是三重循环,枚举所有可能的三元组,然后用哈希表去重。但是这样的方法时间复杂度比较高,而且需要额外的空间。所以需要进行优化。
  • 首先一个可以优化的点是:防止重复三元组的出现。我们可以对nums先进行一个升序排序,然后每次进行一次新的循环时,就可以确保不会出现之前枚举过的数;并且,遇到新遇到的数和之前一次循环相同时,可以跳过这个数,从而防止了重复的三元组的出现。
  • 我们可以进一步优化算法。考虑内部的两层循环,对于它们而言,三元组的其中一个数已经确定,剩余的两个数只需满足nums[j]+nums[k] = -nums[i]即可(设i,j,k分别对应第一,第二,第三重循环)。我们可以使用双指针,在数组的i项之后的区域中寻找可行的两个数。
    注意到以下的事实:当j为一定值时,nums[j]+nums[k]可能的最大取值就是在k = nums.len()-1时取到。所以如果此时nums[j]+nums[k]的值小于-nums[i]时,就只能向右移动j以寻找可能的三元组;否则就向左移动k寻找可能的三元组。因为每个j只对应唯一的k解,所以这样的方法可以将原来的二重循环简化到一次遍历,两个指针从数组的两边向中间移动,直到j==k停止。

代码

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>>res;
        int len = nums.size();
        sort(nums.begin(),nums.end());
        for(int i = 0;i<len;++i)
        {
            if(i == 0||nums[i-1]<nums[i])
            {
                for(int j = i+1,k = len-1;j<k;++j)
                {
                    if(j == i+1||nums[j-1]<nums[j])
                    {
                        while(nums[i]+nums[j]+nums[k]>0&&j<k)
                        {
                            k--;
                        }
                        if(nums[i]+nums[j]+nums[k]==0&&j<k)
                        {
                            res.push_back({nums[i],nums[j],nums[k]});
                            k--;
                        }
                    }
                }
            }
        }
        return res;
    }
};

题目拓展(LeetCode:16)

给定一个包括 n 个整数的数组nums和 一个目标值target。找出 nums中的三个整数,使得它们的和与target最接近。返回这三个数的和。假定每组输入只存在唯一答案。

示例:

输入:nums = [-1,2,1,-4], target = 1
输出:2
解释:与 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。

解题思路

这道题和上面一道题很相似,但是对双指针的用法还是稍有不同的。
具体而言,在第一层循环确定了nums[i]的情况下,接下来的两个数的选择并不是一条简单的判断条件就能确定的,而是要动态的考察可能的解,然后选择最佳解。
因此,我们可以对i之后的数组区域进行一个基于贪心算法的遍历:
一开始,j = i+1,k=nums.size()-1;这样的情况下,我们检查nums[i]+nums[j]+nums[k]的值,如果这个值大于target,那就意味着当前的解可行,并且可能存在一个比当前解更接近target的解nums[i]+nums[j]+nums[k-1],因为这是仅小于当前解的一个可能解。
同样的,如果nums[i]+nums[j]+nums[k]小于target,那么就意味着当前解可行,并且可能存在一个比当前解更接近target的解nums[i]+nums[j+1]+nums[k],因为这是仅大于当前解的一个可行解。
于是,我们就可以通过这种规则来遍历一个数组区域,从而更快的找到所有可行解。

代码

class Solution {
public:
    int threeSumClosest(vector<int>& nums, int target) {
        int res = nums[0]+nums[1]+nums[2];
        int len = nums.size();
        sort(nums.begin(),nums.end());
        for(int i = 0;i<len;++i)
        {
            if(i == 0||nums[i-1]<nums[i])
            {
                int l = i+1,r = len-1;
                while(l<r)
                {
                    int val = nums[i]+nums[l]+nums[r];
                    if(val>target)
                    {
                        r--;
                    }
                    else if(val == target)
                    {
                        return val;
                    }
                    else
                    {
                        l++;
                    }
                    if(abs(val-target)<abs(res-target))
                    {
                        res = val;
                    }
                }
            }
        }
        return res;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值