跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
TypeScript算法

LeetCode 112. 路径总和:两种解法详解

LeetCode 112 路径总和题目要求判断是否存在从根节点到叶子节点的路径和等于目标和。文章对比了迭代 DFS 和递归 DFS 两种解法。迭代法使用栈存储节点及路径和,递归法通过递减目标和实现。两者时间复杂度均为 O(n),递归代码更简洁但深树可能溢出,迭代法可避免栈溢出。重点在于正确识别叶子节点定义及路径范围。

修罗发布于 2026/3/16更新于 2026/6/1429 浏览
LeetCode 112. 路径总和:两种解法详解

二叉树的路径问题算是高频考点之一,今天拆解 LeetCode 简单难度的 112. 路径总和,不仅搞懂题目核心,还会对比两种常用解法(迭代 DFS + 递归 DFS),帮助理清思路、避坑易错点,适合刚接触二叉树路径题的小伙伴入门。

一、题目解读

题目很直白,给定二叉树的根节点 root 和目标和 targetSum,判断是否存在一条从 根节点到叶子节点 的路径,这条路径上所有节点的值相加等于 targetSum。

这里有两个关键细节,一定要注意(否则容易踩坑):

  • 路径必须是「根节点 → 叶子节点」,中间不能中断,也不能是叶子节点到其他节点、非根节点到叶子节点;
  • 叶子节点的定义:没有左、右子节点的节点(即 left === null 且 right === null),这是判断路径终点的核心条件。

举个简单例子:如果二叉树只有根节点,值为 5,targetSum 为 5 → 根节点就是叶子节点,返回 true;如果根节点值为 5,有一个左子节点值为 3,targetSum 为 5 → 没有到叶子节点的路径(根节点不是叶子,左子节点未判断总和),返回 false。

二、核心思路

这道题的核心是「遍历二叉树」,并记录从根节点到当前节点的路径和,当遍历到叶子节点时,判断路径和是否等于目标和。

二叉树的遍历有递归和迭代两种常用方式,对应到这道题,我们可以实现两种解法,本质都是深度优先搜索(DFS)—— 优先走一条路径到叶子,再回溯走其他路径,效率一致,但编码风格不同。

三、完整解法

先给出 TreeNode 的定义(题目已提供,此处复用,保证代码可直接运行):

class TreeNode {
  val: number;
  left: TreeNode | null;
  right: TreeNode | null;
  constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
    this.val = (val === undefined ? 0 : val);
    this.left = (left === undefined ? null : left);
    this.right = (right === undefined ? null : right);
  }
}
解法一:迭代 DFS

迭代方式用「栈」存储遍历的节点,同时记录「从根节点到当前节点的路径和」,每弹出一个节点,就判断是否为叶子节点、路径和是否匹配,不匹配则继续压入其左右子节点(带更新后的路径和)。

下面是题目给出的初始代码,以及我优化后的细节说明:

function hasPathSum_1(root: TreeNode | null, targetSum: number): boolean {
  // 边界条件:空树直接返回 false(没有任何路径)
  if (!root) return false;
  
  // 栈元素:[当前节点,根到当前节点的路径和]
  const stack: [TreeNode, number][] = [[root, root.val]];
  
  // 栈非空,说明还有路径未遍历
  while (stack.length) {
    // 弹出栈顶节点(DFS 特性:后进先出)
    const cur = stack.pop();
    
    // 冗余判断?其实 stack.length>0 时,pop() 不会返回 undefined,可优化
    if (!cur) continue;
    
    // 解构当前节点和路径和
    const [node, sum]: [TreeNode, number] = cur;
    
    // 核心判断:当前是叶子节点 + 路径和等于目标和 → 找到路径,返回 true
    if (!node.left && !node.right && sum === targetSum) return true;
    
    // 左子节点存在,压入栈,路径和更新为「当前和 + 左子节点值」
    if (node.left) stack.push([node.left, sum + node.left.val]);
    
    // 右子节点存在,压入栈,路径和更新为「当前和 + 右子节点值」
    if (node.right) stack.push([node.right, sum + node.right.val]);
  }
  
  // 遍历完所有路径,均不匹配,返回 false
  return false;
}
迭代解法优化点
  • 移除冗余判断:while 循环条件是 stack.length(栈非空),此时 stack.pop() 一定不会返回 undefined,可改用 const [node, sum] = stack.pop()!(TypeScript 断言),省去 if (!cur) continue;
  • 调整子节点压入顺序:先压右子节点,再压左子节点,保证遍历顺序是「左→右」(符合常规 DFS 习惯,不影响结果,但更易理解);
  • 变量命名语义化:将 cur、node、sum 改为 currentNode、currentSum,更直观。
解法二:递归 DFS

递归的核心是「拆分问题」:判断根节点到叶子的路径和,可拆分为「根节点的左子树是否有路径和为 targetSum - root.val」或「根节点的右子树是否有路径和为 targetSum - root.val」,直到递归到叶子节点,直接判断节点值是否等于剩余目标和。

function hasPathSum_2(root: TreeNode | null, targetSum: number): boolean {
  // 递归终止条件 1:空节点(路径中断,没有可行路径)
  if (!root) return false;
  
  // 递归终止条件 2:当前节点是叶子节点 → 判断剩余目标和是否等于当前节点值
  if (!root.left && !root.right) {
    return root.val === targetSum;
  }
  
  // 递归逻辑:遍历左、右子树,目标和减去当前节点值(相当于累计路径和)
  // 只要左子树或右子树有一条路径匹配,就返回 true
  return hasPathSum_2(root.left, targetSum - root.val) || hasPathSum_2(root.right, targetSum - root.val);
}
递归解法核心理解

很多小伙伴刚开始看递归会懵,其实可以这样想:

比如 targetSum 是 10,根节点值是 5,那么我们只需要判断「左子树是否有路径和为 5」或者「右子树是否有路径和为 5」;如果左子节点值是 3,那么继续判断「左子节点的子树是否有路径和为 2」,直到走到叶子节点,直接判断叶子节点值是否等于剩余的目标和。

这种方式不用手动记录路径和,而是通过「目标和递减」的方式间接实现,代码更简洁。

四、两种解法对比

解法时间复杂度空间复杂度优势劣势
迭代 DFS(栈)O(n)(n 为节点数,每个节点遍历一次)O(n)(最坏情况:树退化为链表,栈存储所有节点)避免递归栈溢出(适合极深的二叉树)代码稍长,需要手动维护栈和路径和
递归 DFSO(n)(每个节点遍历一次)O(n)(最坏情况:递归栈深度等于节点数)代码简洁,逻辑清晰,贴合二叉树递归思维树极深时可能出现栈溢出(JavaScript/TypeScript 递归栈深度有限)

总结:日常刷题、面试答题,优先选递归解法(代码简洁,易写易读);如果题目明确说明树可能极深,再用迭代解法规避栈溢出问题。

五、易错点总结

  • 忽略叶子节点定义:误将「只有左子节点或只有右子节点」的节点当作叶子节点,导致判断条件错误(正确条件:!node.left && !node.right);
  • 路径范围错误:误判为「任意节点到叶子节点」,忘记题目要求是「根节点到叶子节点」;
  • 递归终止条件遗漏:递归时未判断空节点,导致报错(空节点没有 val 属性,会触发 TypeError);
  • 迭代时栈元素错误:只存储节点,未存储路径和,导致无法判断当前路径的总和。

六、刷题延伸

掌握这道题后,可以试试同类型的路径题,巩固 DFS 思路:

  • LeetCode 113. 路径总和 II(找出所有根到叶子的路径和等于目标和的路径);
  • LeetCode 437. 路径总和 III(不限制根节点和叶子节点,任意路径和等于目标和)。

七、解题总结

  1. 路径总和是二叉树路径问题的入门题,核心是掌握「DFS 遍历 + 路径和记录/目标和递减」的思路。两种解法没有绝对的优劣,关键是理解其逻辑,根据场景选择合适的实现方式。

目录

  1. 一、题目解读
  2. 二、核心思路
  3. 三、完整解法
  4. 解法一:迭代 DFS
  5. 迭代解法优化点
  6. 解法二:递归 DFS
  7. 递归解法核心理解
  8. 四、两种解法对比
  9. 五、易错点总结
  10. 六、刷题延伸
  11. 七、解题总结
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • Stable Diffusion v4.10 与 ComfyUI 整合包技术说明
  • Llama 3.1 与 Claude Opus 对话实验:安全词机制下的 AI 交互观察
  • WEBGIS:空间数据库创建、数据发布与 WMS 服务调用
  • Llama Factory 模型评估:如何科学衡量微调后的模型性能
  • MAVROS 安装与基础知识梳理及 ROS C++ 仿真案例
  • OpenClaw WebSocket 通道开发实战:构建自定义 AI 通信通道
  • 汽车雷达多径环境下幽灵目标检测算法研究
  • 前端地图基本操作控制:平移、缩放、旋转与样式切换
  • AI 大模型通信机制:深入理解流式传输与数据封装逻辑
  • 技术管理者视角下的低代码价值、边界与演进路径
  • VSCode 与 PyCharm 配置 OpenCV 教程(Python 与 C++)
  • RuoYi-Vue-Plus AI 智能开发助手:Claude Code + Codex 双引擎配置方案
  • Ubuntu 系统下使用 VSCode 编写运行 C++ 程序及 Make CMake 编译配置
  • 基于 LLaMA-Factory 微调 ChatGLM3 模型实战
  • OpenClaw Mac 安装指南
  • 链表经典算法题详解
  • Spring Boot 数据缓存与性能优化实战
  • 算法实战:位运算解决两数之和与缺失数字问题
  • JWT(JSON Web Token)结构化知识体系:从基础到实战
  • PyCharm 集成 GitHub Copilot 插件安装与配置指南

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online