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

如何将二叉树展开为链表?两种Java实现方法对比

文章目录

    • **问题描述**
    • **解决方案**
      • **方法一:迭代法(Morris遍历思路)**
        • **实现步骤**
        • **Java代码实现**
        • **关键解释**
      • **方法二:前序遍历+列表重建**
        • **实现步骤**
        • **Java代码实现**
        • **关键解释**
    • **方法对比与分析**
    • **选择建议**
    • **拓展:方法二的迭代优化**
    • **总结**

问题描述

给定一棵二叉树的根节点 `root``,要求将其按前序遍历的顺序展开为一个单链表。展开后的链表应满足以下条件:

  • 链表的顺序与二叉树的前序遍历结果一致。
  • 链表中每个节点的右子指针指向下一个节点,左子指针始终为 null

示例输入与输出
示例二叉树
输入:root = [1,2,5,3,4,null,6]
输出:[1,null,2,null,3,null,4,null,5,null,6]


解决方案

方法一:迭代法(Morris遍历思路)

核心思想:通过修改指针实现原地展开,无需额外空间。类似Morris遍历,时间复杂度O(n),空间复杂度O(1)。

实现步骤
  1. 初始化当前节点:从根节点开始遍历。
  2. 处理左子树:对于每个节点,若存在左子树,找到左子树的最右节点。
  3. 调整指针
    • 将左子树的最右节点的右指针指向当前节点的右子树。
    • 将当前节点的右指针指向左子树,左指针置空。
  4. 迭代处理:沿右指针处理下一个节点,直到所有节点处理完毕。
Java代码实现
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;}
}public class Solution {public void flatten(TreeNode root) {TreeNode curr = root;while (curr != null) {if (curr.left != null) {// 找到左子树的最右节点TreeNode prev = curr.left;while (prev.right != null) {prev = prev.right;}// 将右子树接到最右节点prev.right = curr.right;// 将左子树移到右侧,并清空左侧curr.right = curr.left;curr.left = null;}// 处理下一个节点curr = curr.right;}}
}
关键解释
  • 左子树的最右节点:前序遍历中,左子树的最后一个节点需要连接到当前节点的右子树。
  • 指针调整:通过修改指针实现原地展开,无需额外空间。

方法二:前序遍历+列表重建

核心思想:显式存储前序遍历结果,再重建链表。时间复杂度O(n),空间复杂度O(n)。

实现步骤
  1. 前序遍历存储节点:递归遍历二叉树,按前序顺序将节点存入列表。
  2. 重建链表:遍历列表,将每个节点的左指针置空,右指针指向下一个节点。
Java代码实现
import java.util.ArrayList;
import java.util.List;class Solution {public void flatten(TreeNode root) {if (root == null || (root.left == null && root.right == null)) return;List<TreeNode> result = new ArrayList<>();preOrder(root, result);// 重建链表for (int i = 0; i < result.size() - 1; i++) {TreeNode prev = result.get(i);TreeNode cur = result.get(i + 1);prev.left = null;prev.right = cur;}}private void preOrder(TreeNode root, List<TreeNode> result) {if (root == null) return;result.add(root);preOrder(root.left, result);preOrder(root.right, result);}
}
关键解释
  • 前序遍历列表:显式存储节点顺序,逻辑直观。
  • 空间开销:需额外存储所有节点的引用,空间复杂度为O(n)。

方法对比与分析

特性方法一(迭代法)方法二(前序遍历+列表)
时间复杂度O(n)O(n)
空间复杂度O(1)O(n)
代码复杂度高(需处理指针调整)低(逻辑直观)
栈溢出风险有(递归深度高时)
适用场景内存敏感、大规模数据快速实现、小规模数据

选择建议

  1. 优先方法一

    • 适用场景:内存受限(如嵌入式开发)、处理超大规模树。
    • 优点:原地修改,无额外空间开销。
    • 缺点:指针操作复杂,需深入理解Morris遍历。
  2. 优先方法二

    • 适用场景:快速实现、代码可读性优先、小规模数据。
    • 优点:逻辑清晰,易于调试。
    • 缺点:空间开销大,递归可能栈溢出。

拓展:方法二的迭代优化

将递归前序遍历改为迭代实现,避免栈溢出风险:

private void preOrder(TreeNode root, List<TreeNode> result) {Deque<TreeNode> stack = new ArrayDeque<>();TreeNode node = root;while (node != null || !stack.isEmpty()) {while (node != null) {result.add(node);stack.push(node);node = node.left;}node = stack.pop();node = node.right;}
}

总结

  • 方法一适合追求极致空间效率的场景,但对代码能力要求较高。
  • 方法二适合快速实现和逻辑清晰的需求,但需权衡空间开销。
  • 两种方法均保证链表的前序顺序正确性,可根据实际需求选择。
http://www.xdnf.cn/news/219799.html

相关文章:

  • FPGA 38 ,FPGA 网络通信协议栈基础,ARP 协议深度解析与模块划分( ARP与以太网帧,以及ARP模块常用文件 )
  • 细说STM32单片机FreeRTOS互斥量及其编程实例
  • C# 导入EXCEL 报错外部表不是预期的格式错误指南方案
  • C++中的vector和list有什么区别?
  • Launcher3-实现家长管控-儿童模式-老人模式
  • 机器学习第四篇 线性回归-最小二乘法
  • 案例分享|20倍提效!水力设备电磁仿真的云端实战
  • DDoS攻击真的无解吗?
  • DeepClaude开源程序可以实现代码生成、创作诗句以及内容创作等功能
  • 详解大语言模型生态系统概念:lama,llama.cpp,HuggingFace 模型 ,GGUF,MLX,lm-studio,ollama这都是什么?
  • 【LaTex】3.8流程图绘制
  • Transformer数学推导——Q34 推导位置插值(Position Interpolation)在长文本外推中的误差上界
  • (02)Redis 的订阅发布Pub/Sub
  • Ubuntu上搭建python环境并安装第三方库
  • C语言教程(二十四):C 语言中递归的详解
  • cuda学习3: 全局线程id计算
  • 大语言模型能否替代心理治疗师的深度拓展研究:fou
  • 两数之和II-输入有序数组(中等)
  • 洛谷题解 | CF1979C Earning on Bets
  • DNA复制过程3D动画教学工具
  • 稳定性 复杂度
  • 浅析localhost、127.0.0.1 和 0.0.0.0的区别
  • 【RocketMq延迟消息操作流程】
  • 鸟笼效应——AI与思维模型【84】
  • Canvas基础篇:概述
  • DeepSeek 本地化部署与 WebUI 配置的方法
  • Fiddler抓取APP端,HTTPS报错全解析及解决方案(一篇解决常见问题)
  • 在Ubuntu中安装python
  • 02_高并发系统问题及解决方案
  • 大模型高效化三大核心技术:量化、蒸馏与剪枝详解