XingHuiSama の 宝藏之地
首页项目归档照片墙杂谈友链关于
封面

Leetcode一百题——三数之和

写作时间:2026-04-09 22:42:59
# Leetcode
# C++
# 题解
# 算法

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。

**注意:**答案中不可以包含重复的三元组。

示例 1:

输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。

示例 2:

输入:nums = [0,1,1]
输出:[]
解释:唯一可能的三元组和不为 0 。

示例 3:

输入:nums = [0,0,0]
输出:[[0,0,0]]
解释:唯一可能的三元组和为 0 。

提示:

  • 3 <= nums.length <= 3000
  • -105 <= nums[i] <= 105

核心题解:三数之和(排序 + 双指针法)

1. 算法核心思想:降维打击

如果是暴力解法,需要三个 for 循环穷举所有的三个数组合,时间复杂度是极其恐怖的 O(n3)。 为了优化,我们采用**“固定老大,双指针找小弟”**的策略,这是一种经典的降维打击:

  • 提前给数组排序(这是双指针能生效的唯一绝对前提)。
  • 用一层 for 循环固定第一个数字(老大 i)。
  • 将寻找另外两个数字的任务,转化为一个在有序数组里寻找目标值 -nums[i] 的**“两数之和”**问题。
  • 使用一前(front)一后(back)两个指针向中间靠拢,时间复杂度成功降维到 O(n2)。

2. 避坑指南:“去重”

这道题 80% 的报错都来自于“答案中包含重复的三元组”。我们在代码中必须布下三道防线:

  • 防线一(极致剪枝):如果排好序后,老大 nums[i] 都已经大于 0 了,说明后面的数字全是正数,再怎么加也不可能等于 0,直接 break 提前下班。
  • 防线二(老大去重):如果当前的老大和上一个老大长得一样,说明这个老大的所有组合在上一轮已经全找过了。必须**“回头看”**(nums[i] == nums[i-1]),如果是,直接 continue 跳过。绝对不能“往后看”,否则会把 [-1, -1, 2] 这种合法的答案误杀。
  • 防线三(小弟去重):当找到一组答案,或者和偏大/偏小时,指针移动的同时,必须用 while 循环跨过所有连续相同的数字,避免重复判断。
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> result;

        // 1. 灵魂前提:必须排序
        sort(nums.begin(), nums.end());
        int n = nums.size();
        
        for (int i = 0; i < n; i++) {
            // 防线一:如果最小的数都大于0,不可能凑成0了
            if (nums[i] > 0) {
                break;
            }
            
            // 防线二:老大去重(注意是回头看 i-1,防止误杀同伙)
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }

            int front = i + 1;
            int back = n - 1;
            
            while (front < back) {
                int sum = nums[i] + nums[front] + nums[back];
                
                if (sum == 0) {
                    // 记录真实的数字,而不是下标
                    result.push_back({nums[i], nums[front], nums[back]});
                    
                    // 找到答案后,左指针先往前走并去重
                    front++;
                    while (front < n && nums[front] == nums[front - 1]) {
                        front++;
                    }
                    // 注:极其巧妙的闭环!此时 front 变大了,下一轮 sum 必然 >0,
                    // 会自动触发下方的 else 分支,从而让 back 也去重并向左移动。
                }
                else if (sum < 0) {
                    // 和太小,左指针往右走(变大),并进行小弟去重
                    front++;
                    while (front < n && nums[front] == nums[front - 1]) {
                        front++;
                    }
                }
                else {
                    // 和太大,右指针往左走(变小),并进行小弟去重
                    back--;
                    while (back > front && nums[back] == nums[back + 1]) {
                        back--;
                    }
                }
            }
        }

        return result;
    }
};

4. 复杂度剖析

  • 时间复杂度:O(n2)。数组排序的时间复杂度是 O(nlogn)。外层遍历 i 是 O(n),内部的双指针 front 和 back 相向而行,最多也是遍历整个剩余数组,时间也是 O(n)。总体算下来是 O(n2)。
  • 空间复杂度:O(1) 或 O**(logn)**。如果不看存储结果的 result 数组,双指针本身只用了常数级的额外空间(O(1))。但在 C++ 中,sort() 函数底层通常会使用快速排序的变体,会占用额外的 O(logn) 的栈空间。
avatar

XingHuiSama

在代码、学术与分子动力学模拟间穿梭的普通人。近期正埋头于 GROMACS 模拟研究与神经网络计算。

RECOMMENDED

GROMACS 2025 分子动力学模拟初探2222

2026-03-24 07:00:45

Computational Chemistry Tool 工具二介绍

2026-04-01 07:00:23

Leetcode一百题——字母异位词分组

2026-04-07 15:34:18

Table of Contents