LeetCode 热题 100——day6 三数之和

✨ 把代码写进星轨,用逻辑丈量宇宙。

导航 链接
个人主页 🏠 星轨初途
基础语言专栏 💻 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 大小移动

思路清楚,去重也比较自然。

🎯 今天的三数之和就拿下啦!有时候第一时间想到的解法不一定是最后写出来最舒服的解法,能从哈希思路一步步转到双指针,本身也是刷题过程中很重要的一步。继续积累,我们下一题再见啦٩(๑❛ᴗ❛๑)۶!

相关推荐
垆边人似月.5 分钟前
城市连通性(200分 / 并查集)
数据结构·算法·图论
我命由我123456 分钟前
Photoshop - Photoshop 使用简单的选择工具
学习·职场和发展·产品运营·求职招聘·职场发展·学习方法·photoshop
西西弗Sisyphus9 分钟前
Qt6 实现 会动的界面像物理世界一样
c++·qt
UIU11417 分钟前
头文件可以防什么错?运用头文件做声明
c++·学习·c#
Gust of wind21 分钟前
串与KMP模式匹配:存储结构、基本操作、next数组手算
c语言·数据结构·后端·算法
我命由我1234523 分钟前
Photoshop - Photoshop 使用工具
学习·职场和发展·产品运营·求职招聘·职场发展·产品经理·学习方法
Navigator_Z27 分钟前
LeetCode //C - 1283. Find the Smallest Divisor Given a Threshold
c语言·算法·leetcode
cc57250265334 分钟前
2027 秋招|大数据 / 统计类专业运营管培群面,数据思维落地方法
面试·职场和发展
by209991 小时前
list:迭代器与资源管理实现的综合叙述(下)
数据结构·c++·笔记·list
啦啦啦啦啦zzzz1 小时前
一致性哈希
服务器·算法·哈希算法·c++20·分布式存储