15. 三数之和
难度中等
题目描述
给你一个整数数组 nums
,判断是否存在三元组 [nums[i], nums[j], nums[k]]
满足 i != j
、i != k
且 j != k
,同时还满足 nums[i] + nums[j] + nums[k] == 0
。请你返回所有和为 0
且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例 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] 。 注意,输出的顺序和三元组的顺序并不重要。
示例 2:
输入:nums = [0,1,1] 输出:[] 解释:唯一可能的三元组和不为 0 。
示例 3:
输入:nums = [0,0,0] 输出:[[0,0,0]] 解释:唯一可能的三元组和为 0 。
提示:
3 <= nums.length <= 3000
-105 <= nums[i] <= 105
解题思路:
本题可以使用最暴力的三重循环来进行对数组里面的数一一枚举出来,但是这样的时间复杂度很高O(N^3),所以我们应该优化此思路,将三数之和问题简化为两数之和问题,并利用双指针操作来进行高效遍历。
- 首先,将k指针定位到首元素,然后在k+1和len-1的位置分别设置双指针i,j
- 然后开始检查nums[k]+nums[i]+nums[j]是否等于0,,如果>0那么使 j 指针向左移动,如果<0那么使 i 指针向右移动,如果==0那么就将i、j指针分别向右、左移动,以避免重复计算。
while(i<j){ // 在剩余的数中搜索第二个数和第三个数
int sum=nums[k]+nums[i]+nums[j]; // 计算当前三个数的和
if(sum>0) j--; // 如果和大于0,则将j指针向左移动一位,使得和变小
else if(sum<0) i++; // 如果和小于0,则将i指针向右移动一位,使得和变大
else{ // 如果和等于0,则将这三个数加入结果数组中,并将i、j指针分别向右、左移动,以避免重复计算
res.push_back({nums[k],nums[i],nums[j]});
while(i<j&&nums[i]==nums[++i]);
while(i<j&&nums[j]==nums[--j]);
}
}
注意:为了减少循环次数,在进行排序后,如果数组的最小值大于0或者数组的最大值小于0,那么直接返回空数组,因为无法相加等于0,并且在循环中遇到重复元素要跳过,避免重复计算!
以下是本人通过代码:
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> res; // 存储结果的二维数组
int len=nums.size(); // 数组长度
sort(nums.begin(),nums.end()); // 对数组进行排序
if(nums[len-1]<0||nums[0]>0) return res; // 如果最大的数都小于0,或者最小的数都大于0,则无法凑出和为0,直接返回空数组
for(int k=0;k<=len-3;k++){ // 枚举第一个数的位置
if(nums[k]>0) break; // 如果第一个数已经为正数,则不可能凑出和为0,直接退出循环
if(k>0&&nums[k]==nums[k-1]) continue; // 避免重复计算,如果当前数字与前面的数字相同,则跳过当前循环
int i=k+1,j=len-1; // 定义双指针,i指向第二个数,j指向最后一个数
while(i<j){ // 在剩余的数中搜索第二个数和第三个数
int sum=nums[k]+nums[i]+nums[j]; // 计算当前三个数的和
if(sum>0) j--; // 如果和大于0,则将j指针向左移动一位,使得和变小
else if(sum<0) i++; // 如果和小于0,则将i指针向右移动一位,使得和变大
else{ // 如果和等于0,则将这三个数加入结果数组中,并将i、j指针分别向右、左移动,以避免重复计算
res.push_back({nums[k],nums[i],nums[j]});
while(i<j&&nums[i]==nums[++i]);
while(i<j&&nums[j]==nums[--j]);
}
}
}
return res; // 返回结果数组
}
};
int main() {
Solution sol; // 创建Solution对象
vector<int> nums; // 定义存储输入数据的向量
vector<vector<int>> res; // 定义存储结果的二维向量
int num;
char c;
while(c!='\n'){ // 循环读入数据
cin>>num; // 读入数字
nums.push_back(num); // 将数字加入向量
c=getchar(); // 读取下一个字符
}
res=sol.threeSum(nums); // 调用threeSum函数,得到结果数组
for(int i=0;i<res.size();i++){ // 遍历结果数组,并输出每个数组中的元素
for(int j=0;j<res[0].size();j++){
cout<<res[i][j]<<" ";
}
cout<<"\n";
}
return 0; // 程序结束
}
做完这道题,还可以类比力扣中16. 最接近的三数之和 这一道题,思路不变,只是改变了问题所求的东西。下面是改编后题目的通过代码:
通过下列代码可以求出最接近目标值的三数之和!
int threeSumClosest(vector<int>& nums,int target) {
int len=nums.size();
sort(nums.begin(),nums.end()); // 排序数组
int res,min=2e9; // 定义结果和最小值初始值
for(int k=0;k<=len-3;k++){ // 固定一个数,转化为两数之和问题
if(k>0&&nums[k]==nums[k-1]) continue; // 避免重复计算
int i=k+1,j=len-1; // 双指针
while(i<j){
int sum=nums[k]+nums[i]+nums[j]; // 计算三数之和
if(fabs(sum-target)<=min){ // 如果更接近目标值,则更新结果和最小值
res=sum;
min=fabs(sum-target);
}
if(sum<target) while(i<j&&nums[i]==nums[++i]); // 如果三数之和小于目标值,移动左指针
else while(i<j&&nums[j]==nums[--j]); // 如果三数之和大于目标值,移动右指针
}
}
return res; // 返回最终结果
}
最后LeetCode中还给我们拓展了18. 四数之和,对于四数之和我们只需要在外层再添加一层循环即可,并且注意:求和过程中可能会遇到溢出问题,这时候就需要一些小技巧改变求和方式即可!
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>> res;
int len=nums.size();
sort(nums.begin(),nums.end());
for(int x=0;x<=len-4;x++){
if(x>0&&nums[x]==nums[x-1]) continue;
for(int k=x+1;k<=len-3;k++){
if(k>x+1&&nums[k]==nums[k-1]) continue;
int i=k+1,j=len-1;
while(i<j){
if(nums[x]+nums[k]-target<-(nums[i]+nums[j])) i++;
else if(nums[x]+nums[k]-target>-(nums[i]+nums[j])) j--;
else{
res.push_back({nums[x],nums[k],nums[i],nums[j]});
while(i<j&&nums[i]==nums[++i]);
while(i<j&&nums[j]==nums[--j]);
}
}
}
}
return res;
}
};
防止溢出办法,原本是(nums[x]+nums[k]+nums[i]+nums[j]<target),这样相加会导致左边溢出,
所以修改为:((long)nums[x]+nums[k]-target<-(nums[i]+nums[j])
注:老版本不需要加上long强制转换,新版本题目把测试数据范围变得更大了,所以需要加long