(LeetCode-Hot100)15. 三数之和

15. 三数之和(3Sum)

🔗 问题简介

LeetCode 中文链接

📝 题目描述

给你一个整数数组 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[0] + nums[1] + nums[4] = (-1) + 0 + (-1) = -2 ≠ 0
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0

示例 2:

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

示例 3:

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

💡 解题思路

✅ 主流解法:排序 + 双指针(推荐)

步骤详解:
  1. 排序数组

    先对 nums 进行升序排序。这一步是为了后续使用双指针,并方便去重。

  2. 固定第一个数(外层循环)

    遍历 i 从 0 到 n - 3(因为至少要留两个位置给 j 和 k):

    • 如果 nums[i] > 0,由于数组已排序,后面不可能再有和为 0 的组合,直接 break。
    • 去重处理 :如果 i > 0 且 nums[i] == nums[i - 1],跳过,避免重复三元组。
  3. 双指针查找另外两个数

    设 left = i + 1,right = n - 1:

    • 计算当前三数之和 sum = nums[i] + nums[left] + nums[right]
    • 若 sum == 0:
      • 将 [nums[i], nums[left], nums[right]] 加入结果
      • 去重 :移动 left 跳过相同值;移动 right 跳过相同值
      • left++, right--
    • 若 sum < 0 → left++
    • 若 sum > 0 → right--
  4. 返回结果


❌ 其他解法(不推荐)

方法 描述 缺点
暴力三重循环 三层 for 循环枚举所有三元组 时间复杂度 O(n³),超时
哈希表优化两重循环 固定两个数,用哈希查第三个 难以有效去重,逻辑复杂,仍可能 O(n²) 空间

💡 结论:排序 + 双指针 是最优解,兼顾时间效率与去重逻辑清晰。


💻 代码实现

java:Java 复制代码
import java.util.*;

public class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        Arrays.sort(nums);
        List<List<Integer>> res = new ArrayList<>();
        int n = nums.length;
        
        for (int i = 0; i < n - 2; i++) {
            // 如果最小值都大于0,不可能有解
            if (nums[i] > 0) break;
            
            // 跳过重复的第一个元素
            if (i > 0 && nums[i] == nums[i - 1]) continue;
            
            int left = i + 1, right = n - 1;
            while (left < right) {
                int sum = nums[i] + nums[left] + nums[right];
                if (sum == 0) {
                    res.add(Arrays.asList(nums[i], nums[left], nums[right]));
                    
                    // 跳过重复的 left 和 right
                    while (left < right && nums[left] == nums[left + 1]) left++;
                    while (left < right && nums[right] == nums[right - 1]) right--;
                    
                    left++;
                    right--;
                } else if (sum < 0) {
                    left++;
                } else {
                    right--;
                }
            }
        }
        return res;
    }
}
go:Go 复制代码
import (
    "sort"
)

func threeSum(nums []int) [][]int {
    sort.Ints(nums)
    var res [][]int
    n := len(nums)
    
    for i := 0; i < n-2; i++ {
        if nums[i] > 0 {
            break
        }
        if i > 0 && nums[i] == nums[i-1] {
            continue
        }
        
        left, right := i+1, n-1
        for left < right {
            sum := nums[i] + nums[left] + nums[right]
            if sum == 0 {
                res = append(res, []int{nums[i], nums[left], nums[right]})
                
                // 去重
                for left < right && nums[left] == nums[left+1] {
                    left++
                }
                for left < right && nums[right] == nums[right-1] {
                    right--
                }
                
                left++
                right--
            } else if sum < 0 {
                left++
            } else {
                right--
            }
        }
    }
    return res
}

🎯 示例演示(以 nums = [-1,0,1,2,-1,-4] 为例)

  1. 排序后:[-4, -1, -1, 0, 1, 2]
  2. i = 0(-4)→ 最小值 -4,最大可能和 -4 + 1 + 2 = -1 < 0 → 无解
  3. i = 1(-1):
    • left=2(-1),right=5(2)→ -1 + (-1) + 2 = 0 → 找到 [-1,-1,2]
    • 移动指针后:left=3(0),right=4(1)→ -1 + 0 + 1 = 0 → 找到 [-1,0,1]
  4. i = 2(-1)→ 与 i=1 相同,跳过(去重)
  5. i = 3(0)→ 0 + 1 + 2 = 3 > 0,无解

✅ 最终结果:[[-1,-1,2], [-1,0,1]]


✅ 答案有效性证明

正确性:

  • 完备性 :通过遍历所有可能的 i,并在每个 i 下用双指针覆盖所有 (left, right) 组合,确保不漏解。
  • 无重复 :
    • 外层 i 跳过与前一个相同的值;
    • 内层在找到解后,left 和 right 同时跳过重复值;
    • 保证每个三元组 (a, b, c) 满足 a ≤ b ≤ c 且唯一。

边界处理:

  • 数组长度 < 3 → 自动返回空(循环不执行)
  • 全零数组 → 正确返回 [[0,0,0]]
  • 无解情况 → 返回空列表

📊 复杂度分析

项目 分析
时间复杂度 O(n log n) + O(n²) = O(n²) 排序 O(n log n),外层循环 O(n),内层双指针 O(n)
空间复杂度 O(1)(不计输出空间) 仅使用常数额外变量(Go/Java 的排序可能用 O(log n) 栈空间)

💡 实际运行效率高,是 LeetCode 官方推荐解法。


📌 问题总结

  • 核心思想 :排序 + 双指针 + 去重
  • 关键技巧 :
    • 利用排序将三数之和转化为"两数之和"问题;
    • 通过跳过重复元素避免无效计算和重复答案;
    • 提前终止(nums[i] > 0 时 break)优化性能。
  • 适用场景:类似"K 数之和"问题(如四数之和)均可借鉴此思路。
  • 易错点 :
    • 忘记去重导致答案重复;
    • 指针移动顺序错误;
    • 边界条件(如数组长度不足)未处理。

github地址: https://github.com/swf2020/LeetCode-Hot100-Solutions

相关推荐
怕浪猫6 小时前
RAG 面试 6 连问,从原理到优化全部覆盖
python·算法·面试
无线通信科研笔记6 小时前
IEEE TVT 2026 论文精读与完整复现|相位误差如何重塑近场 RIS 的幅相响应
论文阅读·人工智能·python·算法·论文笔记
daidaidaiyu7 小时前
ThingsBoard 集群的核心逻辑源码分析
java
mmmmath_37 小时前
二叉树の递归遍历
算法
All for pursuit.8 小时前
【位运算-1】338.比特位计数
数据结构·c++·算法·leetcode
我可能是个假开发8 小时前
FTP Unicode 文件名导致 `550` 的问题分析与解决方案
java·开发语言·spring
caoerzhong8 小时前
中小企业上 WMS 该先上哪几块:JeeWMS 开源 Java 仓库管理系统的分批上线清单
java·python·开源
小师兄吃牛肉8 小时前
C语言中的nullptr有必要吗
c语言·c++·算法
【JAVA】玩家9 小时前
Spring核心原理全解析:从零到生产实战
java·后端·spring
Wang's Blog9 小时前
Java框架 SpringCloud 快速入门: 实现 Feign 最佳实践(抽取方式)
java·spring cloud