LeetCode 15. 三数之和:排序+双指针解法全解析

在LeetCode数组类题目中,"三数之和"是经典的中等难度题,核心考点在于去重逻辑双指针优化。本题不仅要求找到所有和为0的三元组,还需保证结果无重复,直接暴力枚举会因时间复杂度过高和重复结果问题无法通过测试。本文将详细拆解排序+双指针的最优解法,逐行解析代码逻辑,帮你彻底搞懂这道题的解题关键。

一、题目回顾

给定一个整数数组 nums,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足:

  • 下标互不相等(i != j、i != k、j != k

  • 三数之和为0(nums[i] + nums[j] + nums[k] == 0

返回所有满足条件且不重复的三元组。注意:答案中不可包含重复三元组,顺序无关紧要。

二、解题思路:为什么选择"排序+双指针"?

首先分析暴力解法的问题:直接三重循环枚举所有三元组,时间复杂度为 O(n³),对于 n=3000 的测试用例(本题常见规模),会严重超时;同时需要额外处理大量重复三元组,代码复杂度极高。

而"排序+双指针"解法可将时间复杂度优化至 O(n²),且能天然适配去重逻辑,核心思路如下:

  1. 排序预处理:先对数组排序,一方面可通过有序性优化双指针移动逻辑,另一方面能快速跳过重复元素实现去重;

  2. 固定单指针 :遍历数组,将当前元素作为三元组的第一个元素 nums[i],转化为"在剩余元素中找两个数,使其和为 -nums[i]"的两数之和问题;

  3. 双指针找目标 :用左指针 left = i+1(剩余元素起始)、右指针 right = n-1(剩余元素末尾),根据三数之和调整指针位置,找到符合条件的三元组;

  4. 多层去重:对固定的第一个元素、左指针、右指针分别做去重处理,避免生成重复三元组。

三、完整代码与逐行解析

以下是TypeScript实现的最优解法,代码结构清晰,包含关键注释,我们逐段拆解核心逻辑:

typescript 复制代码
function threeSum(nums: number[]): number[][] {
  const res: number[][] = [];
  const n = nums.length;
  
  // 边界条件:数组长度小于3,不可能组成三元组,直接返回空数组
  if (n < 3) return res;
  
  // 1. 排序(核心前提:为双指针移动和去重提供基础)
  nums.sort((a, b) => a - b);
  
  // 2. 固定第一个数,遍历数组作为三元组的第一个元素nums[i]
  for (let i = 0; i < n; i++) {
    // 去重1:当前数与前一个数相同,跳过(避免重复起始元素导致重复三元组)
    if (i > 0 && nums[i] === nums[i - 1]) continue;
    
    // 优化:排序后若当前数>0,后续所有数都>=它,三数之和必>0,直接退出循环
    if (nums[i] > 0) break;
    
    // 双指针初始化:左指针从i+1开始(避免重复使用nums[i]),右指针从数组末尾开始
    let left = i + 1;
    let right = n - 1;
    
    // 双指针遍历剩余元素
    while (left < right) {
      const sum = nums[i] + nums[left] + nums[right];
      
      if (sum === 0) {
        // 找到符合条件的三元组,加入结果集
        res.push([nums[i], nums[left], nums[right]]);
        
        // 去重2:左指针跳过重复值(避免同一nums[i]下,左指针重复导致重复三元组)
        while (left < right && nums[left] === nums[left + 1]) left++;
        // 去重3:右指针跳过重复值(同理,避免右指针重复)
        while (left < right && nums[right] === nums[right - 1]) right--;
        
        // 移动指针,寻找下一组可能的三元组
        left++;
        right--;
      } else if (sum < 0) {
        // 和偏小:左指针右移,增大数值(排序后左指针右移数值递增)
        left++;
      } else {
        // 和偏大:右指针左移,减小数值(排序后右指针左移数值递减)
        right--;
      }
    }
  }
  
  return res;
}

核心逻辑拆解

1. 边界条件处理

当数组长度 n < 3 时,无法组成三元组,直接返回空数组,这是最基础的剪枝操作,避免无效遍历。

2. 排序的关键作用

排序不仅能让双指针根据和的大小调整位置(和小移左、和大移右),更重要的是为去重提供了便利------重复元素会相邻排列,只需跳过相邻重复值即可避免重复三元组。注意排序函数需写全 (a, b) => a - b,否则会按字符串字典序排序,导致结果错误。

3. 固定元素的去重与优化
  • 去重逻辑if (i > 0 && nums[i] === nums[i - 1]) continue。若当前元素与前一个元素相同,说明已以该元素为起始生成过三元组,跳过可避免重复(例如数组 [-1,-1,2],i=1时与i=0元素相同,跳过i=1)。

  • 剪枝优化if (nums[i] > 0) break。排序后数组递增,若当前元素已大于0,后续元素均≥它,三数之和必大于0,无需继续遍历,直接退出循环,大幅提升效率。

4. 双指针的移动与去重

双指针遍历剩余元素时,根据三数之和 sum 的大小调整位置:

  • sum === 0:找到目标三元组,加入结果集。之后需跳过左右指针的相邻重复值(例如左指针下一个元素与当前相同,右移左指针),再移动双指针寻找下一组;

  • sum < 0:和偏小,左指针右移(增大数值),使和向0靠近;

  • sum > 0:和偏大,右指针左移(减小数值),使和向0靠近。

注意:双指针的去重需在找到目标三元组后执行,若提前去重可能跳过有效组合。

四、常见问题与避坑指南

1. 去重逻辑遗漏导致重复三元组

这是本题最容易出错的点,需保证三层去重:固定元素去重、左指针去重、右指针去重。例如未处理左指针重复,会导致 [-1,0,1,-1,2] 生成多个 [-1,0,1]

2. 双指针初始化错误

左指针必须从 i+1 开始,而非0,否则会重复使用固定元素(导致下标重复或三元组重复)。

3. 排序后直接暴力枚举

排序后若仍用三重循环,时间复杂度仍为 O(n³),无法通过大测试用例,双指针是降低时间复杂度的核心。

4. 忽略"和为0但元素重复"的特殊场景

例如数组 [0,0,0],需正确处理固定元素和双指针的重复,最终返回 [[0,0,0]],而非空数组或多个重复三元组。

五、测试用例验证

我们用几个典型测试用例验证代码正确性:

typescript 复制代码
// 测试用例1:常规场景
console.log(threeSum([-1,0,1,2,-1,-4])); 
// 输出:[[-1,-1,2],[-1,0,1]]

// 测试用例2:边界场景(空数组/长度不足3)
console.log(threeSum([])); // 输出:[]
console.log(threeSum([0])); // 输出:[]

// 测试用例3:全零场景
console.log(threeSum([0,0,0])); // 输出:[[0,0,0]]

// 测试用例4:含重复元素场景
console.log(threeSum([-2,0,0,2,2])); // 输出:[[-2,0,2]]

六、复杂度分析

  • 时间复杂度O(n²)。排序时间为 O(n log n),外层循环遍历固定元素为 O(n),内层双指针遍历为 O(n),整体主导项为 O(n²)

  • 空间复杂度O(1)(不计算结果存储)。排序使用的空间取决于语言实现(JavaScript/TypeScript的sort为混合排序,空间复杂度可视为O(1)),双指针仅使用常数额外空间。

七、总结

LeetCode 15. 三数之和的核心是"排序+双指针"的组合策略,通过排序解决去重和指针移动逻辑问题,通过双指针将三重循环优化为二重循环,实现时间复杂度的降级。解题的关键在于精准把控三层去重逻辑双指针的移动规则,同时通过剪枝操作进一步提升效率。

这类"多数之和"问题有通用解题思路:两数之和用哈希表,三数之和/四数之和用"排序+双指针",本质是通过预处理和指针优化降低时间复杂度。掌握本题后,可类比解决 LeetCode 18. 四数之和等同类题目。

相关推荐
崔庆才丨静觅4 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
你撅嘴真丑4 小时前
第九章-数字三角形
算法
passerby60614 小时前
完成前端时间处理的另一块版图
前端·github·web components
uesowys4 小时前
Apache Spark算法开发指导-One-vs-Rest classifier
人工智能·算法·spark
掘了4 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅5 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
ValhallaCoder5 小时前
hot100-二叉树I
数据结构·python·算法·二叉树
董董灿是个攻城狮5 小时前
AI 视觉连载1:像素
算法
崔庆才丨静觅5 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
智驱力人工智能5 小时前
小区高空抛物AI实时预警方案 筑牢社区头顶安全的实践 高空抛物检测 高空抛物监控安装教程 高空抛物误报率优化方案 高空抛物监控案例分享
人工智能·深度学习·opencv·算法·安全·yolo·边缘计算