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 大小移动

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

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

相关推荐
饼饼学习空间智能1 小时前
遥操作数据能否提升机器人自主化水平?详解模仿学习、仿真放大与Sim2Real闭环
人工智能·算法
间歇性努力持续性发呆的野生快乐选手1 小时前
dfs回溯,bfs枚举,完全背包
c++·算法·深度优先·宽度优先
郝学胜_神的一滴2 小时前
干货版《算法导论》17:双数极值配对与卡牌手牌编码最优解
数据结构·算法
码工许师傅2 小时前
一文读懂《Effective C++ 第三版》的55条黄金法则
c++
有点。2 小时前
C++深度优先搜索(三)
开发语言·c++·深度优先
nike0good2 小时前
Codeforces Round 1101 (Div. 2) 题解
算法
中年阿甘3 小时前
解析式布局-二次线长布局
线性代数·算法·矩阵
程序喵大人3 小时前
【C++进阶】STL算法与函数对象 -【C++进阶】STL算法与函数对象
开发语言·c++·算法
我不会插花弄玉3 小时前
10.list【由浅入深-C++】
数据结构·c++·list