题目描述
给你一个包含 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;
}
};