当前位置: 首页 > news >正文

【LeetCode刷题日记】112.递归中的「减法思维」:一题带你打通二叉树路径求和的任督二脉

个人主页北极的代码欢迎来访作者简介java后端学习者❄️个人专栏苍穹外卖日记SSM框架深入JavaWeb✨命运的结局尽可永在不屈的挑战却不可须臾或缺前言大家好我是代码不加冰来到了我们每日的刷题环节由于接近期末周每天自己学习的时间就变得少了但还是要坚持每天学一点到了暑假再猛学两个月想想还有点期待。摘要二叉树路径求和问题是一道经典的递归入门题看似简单却暗含递归思维的底层逻辑。本文以 LeetCode 112 题「路径总和」为例深入剖析两种核心解法递归与迭代。重点阐释递归中「递减目标值」的巧妙之处——通过将路径和问题转化为剩余值问题实现了无状态传递的优雅递归。文章详细拆解了递归函数的执行流程解答了初学者常见的困惑为什么找到答案后还要逐层返回同时提供栈实现的三种迭代写法并对比两者的适用场景。通过本文你将真正理解递归的本质——递是深入探索归是逐层反馈。题目背景给你二叉树的根节点root和一个表示目标和的整数targetSum。判断该树中是否存在根节点到叶子节点的路径这条路径上所有节点值相加等于目标和targetSum。如果存在返回true否则返回false。叶子节点是指没有子节点的节点。示例 1输入root [5,4,8,11,null,13,4,7,2,null,null,null,1], targetSum 22输出true解释等于目标和的根节点到叶节点路径如上图所示。示例 2输入root [1,2,3], targetSum 5输出false解释树中存在两条根节点到叶子节点的路径 (1 -- 2): 和为 3 (1 -- 3): 和为 4 不存在 sum 5 的根节点到叶子节点的路径。示例 3输入root [], targetSum 0输出false解释由于树是空的所以不存在根节点到叶子节点的路径。题目解析我们刚拿到这个题目可以联想到我们之前做过的一道题二叉树的所有路径在那里我们初步认识了回溯并具体实现得到了二叉树的所有路径而我们这题仅仅是要找到某一条或者几条的路径把这条路径的总和加起来看看是否等于我们的目标值路径是从根节点到叶子节点所谓的叶子节点就是没有左右子节点的节点。还有关键的一步就是我们怎么拿到一整条路径上的所有节点的和呢我们传入的是我们的目标值我们在递归的时候每一次递归都能得到当前的节点值那我们就可以拿目标值减去节点值如果最后目标值变成了0那么这条路径就是符合的。ps当然累加也是可以的但是需要额外维护一个状态也就是当前路径的节点值的和。具体的代码实现最直观的方法是深度优先搜索DFS从根节点向下递归遍历每经过一个节点就减去其值到达叶子节点时判断剩余和是否为 0。递归终止条件当前节点为空 → 返回false当前节点是叶子节点无左右孩子→ 返回节点值 targetSum否则递归检查左右子树目标值减去当前节点值java if (root.left null root.right null) { return root.val targetSum; // 叶子节点判断当前值是否等于剩余目标值 }这行代码的意思是如果当前节点是叶子节点判断root.val是否等于targetSum如果相等返回true如果不相等返回false逻辑运算符||的含义java return hasPathSum(root.left, targetSum - root.val) || hasPathSum(root.right, targetSum - root.val);这个||是逻辑或运算符特点短路特性如果左边返回true右边就不会执行只要有一条路径成功整个表达式就为true等价于java // 写法1用 || 简洁版 return dfs(left, newTarget) || dfs(right, newTarget); // 写法2展开后的等价代码 boolean leftResult hasPathSum(root.left, targetSum - root.val); if (leftResult) return true; // 左边找到了就不用查右边 boolean rightResult hasPathSum(root.right, targetSum - root.val); return rightResult;直观理解这句话可以翻译成当前节点到叶子节点的路径和是否能等于targetSum等于问①左子树中能否找到一条路径其和等于targetSum - 当前节点值②右子树中能否找到一条路径其和等于targetSum - 当前节点值只要①或②有一个成立答案就是true递归找到答案后不会瞬间传送回起点而是沿着调用链原路返回每层都执行一次return就像爬梯子一样一级一级退出来。这就是为什么虽然结果在很深的节点就确定了但程序还要执行那么多return的原因——它在清理调用栈把控制权逐级交还给上层。代码实现java class Solution { public boolean hasPathSum(TreeNode root, int targetSum) { // 节点为空没有路径 if (root null) { return false; } // 到达叶子节点判断当前值是否等于剩余目标值 if (root.left null root.right null) { return root.val targetSum; } // 递归左右子树目标值减去当前节点值 return hasPathSum(root.left, targetSum - root.val) || hasPathSum(root.right, targetSum - root.val); } }我们这里也可以使用迭代法用栈来实现。具体逻辑其实还是一样的不过我们可以用一个栈或者两个栈来实现我们入栈的顺序也要考虑好要右孩子先入栈栈是后进先出我们要先处理左孩子。方法一同时存储节点和当前和最直观java class Solution { public boolean hasPathSum(TreeNode root, int targetSum) { if (root null) { return false; } // 栈中存储节点, 从根到该节点的路径和 StackPairTreeNode, Integer stack new Stack(); stack.push(new Pair(root, root.val)); while (!stack.isEmpty()) { PairTreeNode, Integer pair stack.pop(); TreeNode node pair.getKey(); int currentSum pair.getValue(); // 到达叶子节点检查路径和是否等于目标值 if (node.left null node.right null) { if (currentSum targetSum) { return true; } } // 右孩子先入栈因为栈是后进先出左孩子会先处理 if (node.right ! null) { stack.push(new Pair(node.right, currentSum node.right.val)); } if (node.left ! null) { stack.push(new Pair(node.left, currentSum node.left.val)); } } return false; } }方法二使用两个栈更清晰java class Solution { public boolean hasPathSum(TreeNode root, int targetSum) { if (root null) { return false; } // 节点栈和对应的路径和栈 StackTreeNode nodeStack new Stack(); StackInteger sumStack new Stack(); nodeStack.push(root); sumStack.push(root.val); while (!nodeStack.isEmpty()) { TreeNode node nodeStack.pop(); int currentSum sumStack.pop(); // 检查叶子节点 if (node.left null node.right null) { if (currentSum targetSum) { return true; } } // 右孩子先入栈 if (node.right ! null) { nodeStack.push(node.right); sumStack.push(currentSum node.right.val); } if (node.left ! null) { nodeStack.push(node.left); sumStack.push(currentSum node.left.val); } } return false; } }结语如果对你有帮助请点赞关注收藏你的支持就是我最大的鼓励
http://www.zskr.cn/news/1313180.html

相关文章:

  • 【中等】数字字符串转换为字母组合的种数-Java:解法二
  • Google Earth Engine(GEE)——run with profiler查看我们所运行程序的描述、计算指标、内存、峰值内存和数量
  • 基于OpenCV与全志T527的嵌入式手势识别:从算法到工程实践
  • 国产多模态大模型:科学计算领域的“新质生产力”
  • 佛山广州佛山五大校区培训哪家好?全日制培训班推荐 - 检测回收中心
  • 【LLM】code agent bench
  • ChatGPT在软件开发中的实战应用:从代码生成到调试的AI助手指南
  • 用TP4056、PW5300和PW2051搞定你的STM32项目供电:从3.7V锂电池到3.3V/5V的完整电路设计
  • Stripe CLI安全最佳实践:如何保护你的API密钥和敏感数据
  • UVM验证中Sequence启动方式详解:从原理到实战避坑指南
  • 2025最权威的AI学术工具实测分析
  • Win11Debloat:终极Windows系统优化指南,三分钟提升电脑性能38%
  • 钦州金条回收银条回收铂金项链回收克拉钻石回收婚嫁首饰回收本地排名正规门店专业推荐哪家靠谱二手哪家强 - 检测回收中心
  • 如何快速上手ActionView:5分钟完成项目配置和问题创建
  • 3步搭建免费网盘直链解析服务:彻底告别下载限速烦恼
  • 2026届毕业生推荐的十大AI辅助写作方案横评
  • 从业15年网优老兵实话:5G网优工程师发展前景,看完不迷茫
  • 平凉黄金回收白银回收铂金回收钻石回收贵金属回收本地排名正规门店专业推荐哪家靠谱二手哪家强 - 检测回收中心
  • Whetstone.chatgpt:简化ChatGPT Function Calling开发的AI Agent框架
  • 模块化电力电子:标准化接口与软件定义如何重塑能源系统设计
  • NotebookLM权限继承链断裂?揭秘Google Cloud IAM Policy Analyzer在NotebookLM上下文中的3类隐性失效场景
  • 告别繁琐组态:用SVG + JavaScript 5分钟为你的工业设备创建可交互HMI组件
  • 上海髋关节置换医院怎么选?从核心维度拆解选型逻辑 - 奔跑123
  • 把 SAP Central Business Configuration 的 Implementation Workspace 搭起来,别把云实施做成另一个 SPRO
  • Loop:让Mac窗口管理变得优雅而高效
  • yutu项目解析:模块化AI开发工具集如何加速LLM应用构建
  • 基于MCP协议的AI求职助手部署与实战指南
  • The founder‘s playbook: Building an AI-native startup创始人手册:打造原生AI初创企业
  • 在长期项目中体会Taotoken多模型聚合带来的灵活性
  • SAP Cloud 初始访问的第一颗纽扣,IT Contact 与 Initial Admin User 的治理逻辑