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

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

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

相关推荐
lucas_AI6 小时前
把表格压成 64 个 token,长文档问答反而更准了:先找表,再看数
人工智能·深度学习·算法
小小杨树6 小时前
【C++】学习:双缓冲视频流水线深解析
c++·性能优化·音视频开发
Zzz 小生6 小时前
Lazy Theta*:把昂贵的视线检测留到“真正需要”时再做
人工智能·算法·贪心算法·推荐算法
l1258657 小时前
# LangGraph Deep Research Agent 全流程设计:多轮研究、人机协同与真实来源管理
数据库·人工智能·python·算法·自然语言处理·oracle·langchain
我命由我123457 小时前
财务学习 - 配比原则
学习·职场和发展·产品运营·求职招聘·职场发展·产品经理·学习方法
青少儿编程课堂7 小时前
图形化编程实战:智能交通灯调度台,一个作品讲透循环、条件与广播
c++·python·算法·bfs·信息学竞赛
evans在进步7 小时前
LeetCode 438 找到字符串中所有字母异位词:滑动窗口与排序解法详解
算法·leetcode·职场和发展
赵民勇7 小时前
C++无锁编程详解
c++
M78佐菲7 小时前
Linux多线程:创建、回收与互斥同步
linux·笔记·学习·算法
醉颜凉7 小时前
C++ 中野指针与悬挂指针的区别:从成因到防范,彻底讲清两种危险指针
c++