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

LeetCode 257. 二叉树所有路径的迭代解法:栈的妙用与类型设计深度解析

一、题目背景与核心需求

题目描述

LeetCode 257. 二叉树的所有路径要求我们返回从根节点到所有叶子节点的路径。例如,对于二叉树:

    1/ \2   3/
4

应返回 ["1->2->4", "1->3"]。这类问题本质上是要求我们完成深度优先遍历(DFS),并在遍历过程中记录路径信息。

核心难点

  1. 路径动态构建:如何在遍历过程中实时记录从根到当前节点的路径。
  2. 遍历顺序控制:确保每条路径都是从根到叶子的完整路径。
  3. 非递归实现:使用栈结构模拟递归过程,避免递归可能带来的栈溢出问题。

二、迭代解法的核心设计:栈的双元素存储策略

代码实现

/*** Definition for a binary tree node.* public class TreeNode {*     int val;*     TreeNode left;*     TreeNode right;*     TreeNode() {}*     TreeNode(int val) { this.val = val; }*     TreeNode(int val, TreeNode left, TreeNode right) {*         this.val = val;*         this.left = left;*         this.right = right;*     }* }*/
class Solution {public List<String> binaryTreePaths(TreeNode root) {List<String> res = new ArrayList<>();if (root == null) {return res;}Stack<Object> path = new Stack<>();path.push(root);path.push(root.val + "");while (!path.empty()) {String tempPath = (String) path.pop();TreeNode node = (TreeNode) path.pop();if (node.left == null && node.right == null) {res.add(tempPath);}if (node.right != null) {path.push(node.right);path.push(tempPath + "->" + node.right.val);}if (node.left != null) {path.push(node.left);path.push(tempPath + "->" + node.left.val);}}return res;}
}

核心设计解析:为什么选择Object类型的栈?

1. 栈的双重角色:节点与路径的绑定存储
  • 该解法的核心创新在于使用一个栈同时存储两种类型的数据:

    • TreeNode类型:记录当前遍历的节点位置。
    • String类型:记录从根到当前节点的路径字符串。
  • Object类型的必要性
    Java的泛型栈通常只能存储单一类型,但本题需要同时存储TreeNodeString,因此选择Stack<Object>作为容器,利用Java的自动装箱和拆箱机制实现多类型存储。

2. Object类型的优缺点
  • 优点
    灵活应对多类型数据存储,用一个栈完成节点遍历和路径记录,代码简洁高效。

  • 缺点
    需要显式类型转换(如(String)(TreeNode)),若入栈出栈顺序错误会导致类型转换异常,对编程逻辑的严谨性要求更高。

三、出入栈逻辑深度解析:LIFO特性的巧妙应用

1. 初始入栈:根节点与初始路径

path.push(root);
path.push(root.val + "");
  • 入栈顺序:先压入根节点,再压入根节点值的字符串表示。
  • 栈内状态:[root, "1"](假设根节点值为1)。

2. 出栈处理:先路径后节点的顺序

String tempPath = (String) path.pop();
TreeNode node = (TreeNode) path.pop();
  • 出栈顺序与入栈顺序相反(LIFO),先取出路径字符串,再取出节点。
  • 这种设计确保了每次处理节点时,路径字符串已正确记录到该节点的路径。

3. 子节点入栈:先右后左的策略

if (node.right != null) {path.push(node.right);path.push(tempPath + "->" + node.right.val);
}
if (node.left != null) {path.push(node.left);path.push(tempPath + "->" + node.left.val);
}
  • 入栈顺序分析:先压入右子节点,再压入左子节点。
  • LIFO特性的应用:由于栈的后进先出特性,左子节点会先于右子节点出栈,从而实现先左后右的深度优先遍历顺序。

4. 栈操作模拟:以示例二叉树为例

示例二叉树:
    1/ \2   3/
4
栈操作流程:
  1. 初始状态
    栈:[1, "1"]

  2. 第一次循环

    • 出栈:"1"1
    • 检查是否为叶子节点:否
    • 入栈右子节点3:[3, "1->3"]
    • 入栈左子节点2:[2, "1->2"]
      栈状态:[3, "1->3", 2, "1->2"]
  3. 第二次循环

    • 出栈:"1->2"2
    • 检查是否为叶子节点:否
    • 入栈右子节点(无),入栈左子节点4:[4, "1->2->4"]
      栈状态:[3, "1->3", 4, "1->2->4"]
  4. 第三次循环

    • 出栈:"1->2->4"4
    • 检查是否为叶子节点:是,加入结果res
      栈状态:[3, "1->3"]
  5. 第四次循环

    • 出栈:"1->3"3
    • 检查是否为叶子节点:是,加入结果res
      栈状态:空
  6. 最终结果["1->2->4", "1->3"]

四、栈结构与递归的等价性分析

1. 迭代栈与递归栈的映射关系

递归实现迭代栈实现
递归调用栈自动保存上下文手动维护栈保存节点和路径
递归返回时自动回溯栈出栈时自然回溯
代码简洁但可能栈溢出代码复杂但可控性更强

2. 时间与空间复杂度对比

  • 时间复杂度:两种方法均为O(n),n为节点数,每个节点仅访问一次。
  • 空间复杂度
    • 递归:O(h),h为树高,取决于递归栈深度。
    • 迭代:O(n),最坏情况下栈需存储所有节点(如链表树)。

3. 大厂面试中的选择策略

  • 递归解法:适合快速实现,代码简洁,适合树高较小的场景。
  • 迭代解法:适合处理大规模数据,避免栈溢出,体现对数据结构的灵活运用能力。
  • 面试建议:当被问及该题时,可先给出递归解法,再主动补充迭代解法,说明两种方法的适用场景。

五、Object栈的潜在风险与优化方案

1. 类型安全风险

  • 风险点:若入栈顺序错误(如先压入路径再压入节点),会导致类型转换异常。
  • 示例错误
    path.push(root.val + ""); // 错误顺序
    path.push(root);
    
    出栈时会先取节点,再取路径,导致(String) path.pop()抛出ClassCastException

2. 优化方案:使用自定义类封装

// 封装节点与路径
class NodePath {TreeNode node;String path;NodePath(TreeNode node, String path) {this.node = node;this.path = path;}
}Stack<NodePath> path = new Stack<>();
path.push(new NodePath(root, root.val + ""));
  • 优点
    类型安全,避免强制类型转换,代码可读性更强。
  • 缺点
    增加类定义开销,代码量稍增。

3. 性能优化:字符串拼接方式

当前代码使用tempPath + "->" + node.val,每次拼接都会生成新字符串。可优化为StringBuilder

path.push(new NodePath(node, new StringBuilder(tempPath).append("->").append(node.val).toString());

减少字符串对象创建,提升性能。

六、总结:迭代解法的核心思想

1. 栈的双重功能

  • 既是节点遍历的容器,也是路径记录的载体,通过Object类型实现多数据类型存储。

2. 出入栈顺序的本质

  • 入栈顺序(先右后左)与出栈顺序(先左后右)的配合,本质上是利用栈的LIFO特性模拟深度优先遍历的顺序。

3. 算法设计的核心原则

  • 状态绑定:将节点与路径绑定存储,确保遍历过程中路径的实时性。
  • 回溯自动化:利用栈的出栈操作自然实现回溯,无需手动管理状态恢复。

这种迭代解法充分展示了数据结构的灵活性——一个看似简单的Object栈,通过精心设计的出入栈顺序,完美模拟了递归的深度优先遍历过程,同时实现了路径的动态构建。理解这种设计思想,对解决类似的路径问题(如二叉树路径和、N叉树路径等)具有重要的借鉴意义。

相关文章:

  • Enhancing Relation Extractionvia Supervised Rationale Verifcation and Feedback
  • Starrocks的CBO基石--统计信息的来源 StatisticAutoCollector
  • Vue 3.0学习目录
  • 制作一款打飞机游戏54:子弹编辑UI
  • SQLite 创建表
  • Axios中POST、PUT、PATCH用法区别
  • 骰子游戏(2023睿抗省赛)
  • wireshark: Display Filter Reference
  • MqSQL
  • 《黄帝内经》数学建模与形式化表征方式的重构
  • SQL基础概念以及SQL的执行方式
  • AWS中国区中API Gateway中403的AccessDeniedException问题
  • 将MCP(ModelContextProtocol)与Semantic Kernel集成(调用github)
  • FPGA:CLB资源以及Verilog编码面积优化技巧
  • Redis 基础知识详解
  • Axure安装与基础
  • 用 Deepseek 写的 html+js 密码生成器
  • 杰发科技AC7801——PWM获取固定脉冲个数
  • 遨游三防科普:三防平板是什么?有什么特殊功能?
  • 平时使用电脑,如何去维护
  • h5营销型网站功能/百度seo竞价推广是什么
  • 维护网站要做哪些工作/最新做做网站
  • 云主机网站/腾讯企点app
  • 做网站用什么样的电脑/市场营销七大策略
  • 做美陈3d模型网站/短信广告投放软件
  • 锦州做网站公司/it培训机构排名前十