力扣刷题#7:LeetCode15三数之和

这道题让我第一次意识到:双指针原来有两种——之前学的都是同向的,这次遇到了相向的。


题目

给你一个整数数组,找出所有和为 0 且不重复的三元组。

输入: [-1, 0, 1, 2, -1, -4]
输出: [[-1, -1, 2], [-1, 0, 1]]

一点思路都没有

拿到题的时候脑子是空的。三个数?怎么找?暴力三重循环 O(n³) 肯定不行。哈希表?存什么?怎么去重?

我唯一确定的是:这题肯定可以优化到 O(n²),因为 O(n³) 太蠢了。

有了这个信念,我开始想:能不能把三数之和转化成已知的问题?


关键顿悟:三数之和 = 固定一个 + 两数之和

三数之和就是:

对于数组的每个数 nums[i]:
    剩下的事情 = 在 i 后面找两个数,加起来等于 -nums[i]

这样就把"三数之和"变成了"一个固定数 + 两数之和"。

但两数之和怎么找?哈希表可以,但去重很麻烦。而且题目不要求下标,只要求值——有没有更好的方法?


为什么要排序

接下来是最关键的一个问题:排序是为了什么?

起初我不理解——排序不是多此一举吗?但答案很直接:

不排序 排序后
不知道数的大小关系 数组从小到大排列
双指针没有方向感 left++ 和变大,right-- 和变小
去重很难(相同值散落各处) 跳过相邻重复,一句话搞定
不能提前结束 nums[i] > 0 时直接结束

排序的 O(n log n) 相对于 O(n²) 来说几乎可以忽略不计。排序让无序变得有序,让指针有了方向。


双指针的两种模式

之前我刷 26/27/283 的时候学的都是快慢指针

模式 A:同向而行(快慢指针)
26 题:慢指针 k 写入,快指针 i 扫描
27 题:同上
283 题:同上

但三数之和用的是另一种:

模式 B:相向而行(左右夹逼)
left 指向最小数(左端),right 指向最大数(右端)
三数和 = nums[i] + nums[left] + nums[right]

如果和 < 0 → 需要更大的数 → left++(左指针右移)
如果和 > 0 → 需要更小的数 → right--(右指针左移)
             ↑                 ↑
         往同一个目标靠近     往同一个目标靠近

两者都是双指针,但用法完全不同。 一个是一快一慢同向走,一个是两端往中间夹逼。想清楚用哪种,取决于数据是有序的还是需要覆盖的。


我的通过代码

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        if (nums.size() < 3) return {};
        sort(nums.begin(), nums.end());

        vector<vector<int>> res;
        int len = nums.size();

        for (int i = 0; i < len - 2; i++) {
            // 跳过重复的固定数
            if (i > 0 && nums[i] == nums[i - 1]) continue;

            int l = i + 1;
            int r = len - 1;

            while (l < r) {
                int sum = nums[i] + nums[l] + nums[r];
                if (sum == 0) {
                    res.push_back({nums[i], nums[l], nums[r]});
                    l++;
                    r--;
                    // 跳过重复的左右指针
                    while (l < r && nums[l] == nums[l - 1]) l++;
                    while (l < r && nums[r] == nums[r + 1]) r--;
                } else if (sum > 0) {
                    r--;  // 和太大,右指针左移
                } else {
                    l++;  // 和太小,左指针右移
                }
            }
        }
        return res;
    }
};

学到的东西

1. 把陌生问题转化成熟悉问题

"三数之和"我不会,但"两数之和"我会。三数之和 = 固定一个 + 两数之和。拆解之后,问题就变成了已经会的东西。

2. 双指针有两种

之前以为双指针就是快慢指针,今天才知道还有左右夹逼。以后遇到有序数组的求和问题,优先想相向双指针。

3. 排序是为了给指针方向

排序不总是"多此一举"。当排序能让问题的复杂度降一个数量级时,它就是值得的。

4. 去重要在三个地方

  • 外层 for 跳过重复的固定数(nums[i] == nums[i-1]
  • 找到一组后跳过重复的 left(nums[l] == nums[l-1]
  • 找到一组后跳过重复的 right(nums[r] == nums[r+1]

少一个,结果里就会多出重复的三元组。


下一步

模式 B 的双指针不只用于三数之和,「盛最多水的容器」「两数之和 II」「四数之和」都是同一类题。掌握了这个模式,后面几道就是套模板了。


写于 2026-06-06。从两数之和到三数之和,中间差的不是代码能力,是想不想得到"排序 + 双指针夹逼"这个组合。

本文档由 AI 辅助生成,作者提供问题和思路,综合获得以上内容。

posted @ 2026-06-06 00:17  STA_running  阅读(5)  评论(0)    收藏  举报