力扣15. 三数之和
题目来源(力扣):
https://leetcode.cn/problems/3sum/description/
题目描述:
给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
基本思路:
相对于 力扣1.两数之和 以及 力扣454. 四数相加 II,此题难度陡然上升。
因为 相对于 力扣1.两数之和 ,此题目需要找3个位置;相对于 力扣454. 四数相加 II ,此题目要求在1个数组而非3个数组中找数。
因此,如果采用哈希法,难度较高(代码在最后给出),需要处理重复问题,每次修改i后需要重新建立一张哈希表等
此题最佳的解法为双指针法,先对原数据进行排序,这里使用快排sort即可,
当i固定时,设j=i+1,k=nums.size()-1,
然后根据val=nums[i]+nums[j]+nums[k]的值来调整j、k的位置,
具体而言,val > 0则k--、val<0则j++,val==0则得到一组答案
参考《代码随想录》中写法,如下:
代码实现:
class Solution
{
public:
vector<vector<int>> threeSum(vector<int> &nums)
{
vector<vector<int>> ans;
sort(nums.begin(), nums.end()); // 对原数据排序
for (int i = 0; i < nums.size(); i++)
{
if (nums[i] > 0)
break; // 排序后如果nums[i]>0,而之后的数>=nums[i],则不能有有答案组了,直接退出循环
if (i > 0 && nums[i] == nums[i - 1])
continue; // 对nums[i]去重,这样的去重方法还会在“回溯算法”的章节中再次看到
for (int j = i + 1, k = nums.size() - 1; j < k;)
{
int val = nums[i] + nums[j] + nums[k];
if (val > 0)
k--;
else if (val < 0)
j++;
else
{
ans.push_back({nums[i], nums[j], nums[k]});
// 找到一组答案后才会去重,防止j>=k
while (j < k && nums[k] == nums[k - 1])
k--;
while (j < k && nums[j] == nums[j + 1])
j++;
// 找到答案后,j和k同时收缩
k--, j++;
}
}
}
return ans;
}
};
时间复杂度O(n^2)
哈希法如下
class Solution
{
public:
vector<vector<int>> threeSum(vector<int> &nums)
{
vector<vector<int>> ans;
sort(nums.begin(), nums.end());
// a=nums[i] b=nums[j] c=nums[k]
for (int i = 0; i < nums.size(); i++)
{
if (nums[i] > 0)
break;
if (i > 0 && nums[i] == nums[i - 1])//对nums[i]去重
continue;
unordered_set<int> m; // 每次确定起点i后,重新建立哈希表
for (int j = i + 1; j < nums.size(); j++)
{
if (j > i + 2 && nums[j] == nums[j - 1] && nums[j - 1] == nums[j - 2])//对nums[j]
去重
continue;
int val = -nums[i] - nums[j];
if (m.find(val) != m.end())
{
ans.push_back({nums[i], nums[j], val});
m.erase(val);
}
else
m.insert(nums[j]);
}
}
return ans;
}
};
时间复杂度O(n^2),但是有较大的常数
因为每次确定起点i后,需要重新建立哈希表
补充
这里去重的方式可以结合后面在回溯算法中的搜索树来进行理解
具体而言就是力扣40. 组合总和 II
浙公网安备 33010602011771号