
✨ 把代码写进星轨,用逻辑丈量宇宙。
| 导航 | 链接 |
|---|---|
| 个人主页 | 🏠 星轨初途 |
| 基础语言专栏 | 💻 C语言、📚 数据结构 |
| 刷题实战专栏 | 🚀 算法及编程题分享、📝 力扣每日刷题分享 |
🚀 刷题继续!今天来到经典的「三数之和」。一开始我看到"找另外一个数",第一反应其实是用哈希来查,写着写着发现去重和下标处理越来越麻烦,最后换成了更舒服的
排序 + 双指针。
文章目录
三数之和

最开始的思路:哈希查找
我一开始的想法其实比较直接。
先枚举两个数字:
text
nums[i] + nums[j]
那么第三个数字一定是:
text
re = -(nums[i] + nums[j])
所以只需要提前记录每个数字是否出现过,再通过哈希快速判断 re 是否存在即可。
也就是把问题转化成:
text
固定 nums[i]
枚举 nums[j]
哈希查找 re
这个思路本身是可以做到 O(n²) 的。
但实际写的时候会遇到几个比较麻烦的问题(╯︵╰):
- 三个数字必须来自不同下标;
- 数组中可能存在重复数字;
- 同一个三元组不能重复加入答案;
- 像
[0,0,0]这种情况还要考虑某个数字出现的次数。
所以我最开始还想着用数组记录数字出现次数、出现位置,再用 set 去重,但写下来比较繁琐。
既然核心复杂度都是 O(n²),那不如先排序,再利用双指针解决,代码和去重逻辑都会清晰很多。
方法:排序 + 双指针
这道题可以理解成:
固定一个数,然后在它右边寻找两个数,使三个数之和等于 0。
首先对数组排序:
cpp
sort(nums.begin(), nums.end());
然后枚举第一个数字 nums[i]。
对于剩下两个数字,使用双指针:
text
left = i + 1
right = n - 1
计算:
cpp
sum = nums[i] + nums[left] + nums[right];
因为数组已经排好序:
text
sum < 0
说明和太小,需要让较小的数字变大:
cpp
left++;
如果:
text
sum > 0
说明和太大,需要让较大的数字变小:
cpp
right--;
如果:
text
sum == 0
说明找到一个答案:
cpp
{nums[i], nums[left], nums[right]}
记录后继续移动左右指针寻找其他答案。
去重处理
这道题比较关键的地方就是去重。
第一个数字去重
cpp
if (i > 0 && nums[i] == nums[i - 1])
continue;
如果当前 nums[i] 和前一个相同,那么之前已经以这个数字作为第一个数字寻找过答案,直接跳过。
左右指针去重
找到答案后:
cpp
while (left < right && nums[left] == nums[left + 1])
left++;
while (left < right && nums[right] == nums[right - 1])
right--;
跳过相同数字,防止得到重复三元组。
一个小优化
因为数组已经从小到大排序。
如果:
cpp
nums[i] > 0
那么 nums[i] 后面的数字肯定也都大于 0。
三个正数相加不可能得到 0,所以可以直接:
cpp
break;
结束循环。
复杂度分析
排序需要:
text
O(nlogn)
外层枚举一个数字,内部双指针最多遍历一遍:
text
O(n²)
所以整体:
- 时间复杂度:
O(n²); - 空间复杂度:
O(1)(不计算返回答案以及排序本身可能使用的额外空间)。
代码
cpp
class Solution
{
public:
vector<vector<int>> threeSum(vector<int>& nums)
{
vector<vector<int>> ans;
// 先排序,方便使用双指针和去重
sort(nums.begin(), nums.end());
int n = nums.size();
// 枚举三元组中的第一个数字
for (int i = 0; i < n - 2; i++)
{
// 当前数字已经大于0,后面全部大于0,不可能得到和为0
if (nums[i] > 0)
break;
// 第一个数字去重
if (i > 0 && nums[i] == nums[i - 1])
continue;
// 在 i 后面的区间使用双指针
int left = i + 1;
int right = n - 1;
while (left < right)
{
int sum = nums[i] + nums[left] + nums[right];
// 和太小,让左边数字变大
if (sum < 0)
{
left++;
}
// 和太大,让右边数字变小
else if (sum > 0)
{
right--;
}
// 找到一个三元组
else
{
ans.push_back({nums[i], nums[left], nums[right]});
// 左指针去重
while (left < right && nums[left] == nums[left + 1])
left++;
// 右指针去重
while (left < right && nums[right] == nums[right - 1])
right--;
left++;
right--;
}
}
}
return ans;
}
};
总结
| 方法 | 核心思路 | 时间复杂度 | 特点 |
|---|---|---|---|
| 哈希思路 | 枚举两个数,哈希查找第三个数 | O(n²) |
思路直接,但下标和去重处理比较麻烦 |
| 排序 + 双指针 | 固定一个数,左右指针寻找另外两个数 | O(n²) |
去重方便,代码更加清晰 |
我最开始其实就是按照哈希的方向写的:
text
nums[i] + nums[j] + re = 0
推出:
text
re = -(nums[i] + nums[j])
然后查 re 是否存在。
但是写到后面发现,真正麻烦的并不是"能不能找到第三个数",而是怎么保证三个下标不同,以及怎么去掉重复答案。
排序之后这个问题就舒服很多了:
text
固定一个数
↓
左指针 → ← 右指针
↓
根据 sum 大小移动
思路清楚,去重也比较自然。
🎯 今天的三数之和就拿下啦!有时候第一时间想到的解法不一定是最后写出来最舒服的解法,能从哈希思路一步步转到双指针,本身也是刷题过程中很重要的一步。继续积累,我们下一题再见啦٩(๑❛ᴗ❛๑)۶!