(leetcode)力扣100 36二叉树的中序遍历(迭代递归)

题目

给定一个二叉树的根节点 root ,返回 它的 中序 遍历 。

数据范围

树中节点数目在范围 [0, 100] 内

-100 <= Node.val <= 100

测试用例

示例1

java 复制代码
输入:root = [1,null,2,3]
输出:[1,3,2]

示例2

java 复制代码
输入:root = []
输出:[]

示例3

java 复制代码
输入:root = [1]
输出:[1]

题解1(迭代 时空On)

java 复制代码
/**
 * 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<Integer> inorderTraversal(TreeNode root) {
       List<Integer> res=new LinkedList<>();
       Deque<TreeNode> deque=new LinkedList<>();
       while(root!=null||!deque.isEmpty()){
            while(root!=null){
                deque.push(root);
                root=root.left;
            }
            root=deque.poll();
            res.add(root.val);
            root=root.right;
       }
       return res;
    }
}

题解2 (递归 时空on)

java 复制代码
class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<Integer>();
        inorder(root, res);
        return res;
    }

    public void inorder(TreeNode root, List<Integer> res) {
        if (root == null) {
            return;
        }
        inorder(root.left, res);
        res.add(root.val);
        inorder(root.right, res);
    }
}

题解3 (Morris中序遍历,时间On,空间O1 )

java 复制代码
/**
 * Definition for a binary tree node.
 * ... (省略 TreeNode 定义) ...
 */
class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> res = new LinkedList<>();
        TreeNode predecessor = null; // 用于存储当前节点的前驱节点(即左子树中最右边的节点)

        while (root != null) {
            // 情况 1: 如果当前节点有左子树
            if (root.left != null) {
                // 1.1 找到当前节点的前驱节点 (predecessor)
                predecessor = root.left;
                // 不断向右走,直到右节点为空,或者右节点已经指向了当前 root(说明线索已建立)
                while (predecessor.right != null && predecessor.right != root) {
                    predecessor = predecessor.right;
                }

                // 1.2 判断是第一次来到这里,还是第二次来到这里
                
                // 如果前驱节点的右指针为空,说明这是第一次访问该节点
                if (predecessor.right == null) {
                    predecessor.right = root; // 【建立线索】:让前驱节点的右指针指向当前节点
                    root = root.left;         // 当前节点向左移动
                } 
                // 如果前驱节点的右指针已经指向当前 root,说明左子树已经遍历完了
                else {
                    res.add(root.val);        // 【中序遍历关键】:左子树处理完,记录当前节点的值
                    predecessor.right = null; // 【断开线索】:恢复树的原始结构
                    root = root.right;        // 当前节点向右移动
                }
            } 
            // 情况 2: 如果当前节点没有左子树
            else {
                res.add(root.val); // 【中序遍历关键】:直接记录当前节点的值
                root = root.right; // 向右移动(如果有线索,会通过线索回到上层节点)
            }
        }

        return res;
    }
}

思路

这道题是最简单的二叉树中序遍历,就不过多讲解普通的迭代算法与递归算法了,记住就行了,关于morris算法。

Morris 遍历的核心思想是利用树中大量的空闲指针(叶子节点的左右孩子空指针)。对于中序遍历(左 -> 根 -> 右),我们需要在遍历完左子树后,能够自动回到"根"节点。

在递归或栈迭代中,我们靠栈来记录"回家的路"。在 Morris 算法中,我们靠动态修改树的结构(建立临时连接)来记录。

核心步骤拆解:

算法主要处理三种情况:

  1. 没有左孩子 (root.left == null):
  • 既然没有左边,那就该轮到打印当前节点了。

  • 打印完后,直接去右边 (root = root.right)。

  • 注意:这里的"右边"可能是真的右子树,也可能是一条指向祖先节点的"线索"(通过之前建立的连接)。

  1. 有左孩子,且是"第一次"到达该节点:
  • 我们需要去遍历左子树,但去之前要给自己留条后路。

  • 找前驱 (Predecessor): 也就是左子树里最右边的那个节点。在标准的中序遍历中,这个前驱节点的下一个节点就应该是当前的 root。

  • 搭桥 (Threading): 我们把前驱节点的 right 指针指向当前 root (predecessor.right = root)。

  • 然后放心地向左走 (root = root.left)。

  1. 有左孩子,且是"第二次"到达该节点:
  • 当我们遍历完左子树后,最后一步会通过刚才搭的那个"桥"回到当前的 root。

  • 此时,我们再次寻找前驱,会发现 predecessor.right == root。这就意味着我们是"穿越"回来的。

  • 拆桥: 把 predecessor.right 改回 null,恢复树的原貌。

  • 处理当前节点: 将 root.val 加入结果集。

  • 向右走: 左边和中间都处理完了,现在去右边 (root = root.right)。

举例演示

假设树结构为:2 -> 1 (左), 3 (右)

  1. 开始在 2: 有左子树 (1)。找到 1 是前驱。
  • 把 1 的 right 指向 2(搭桥)。

  • Current 移到 1。

  1. 现在在 1: 没有左子树。
  • 记录 1。

  • Current 移到 right。因为刚才搭了桥,1 的 right 指向 2。所以 Current 回到了 2。

  1. 回到 2: 有左子树 (1)。再次找前驱 1。
  • 发现 1 的 right 已经指向 2 了(说明是第二次来)。

  • 记录 2。

  • 断开桥 (1 的 right 设为 null)。

  • Current 移到 2 的 right (即 3)。

  1. 现在在 3: 没有左子树。
  • 记录 3。

  • Current 移到 right (null)。

  1. 结束。 结果:[1, 2, 3]。
相关推荐
hcnaisd21 分钟前
深入理解C++内存模型
开发语言·c++·算法
李老师讲编程8 分钟前
C++信息学奥赛练习题-杨辉三角
数据结构·c++·算法·青少年编程·信息学奥赛
zxsz_com_cn18 分钟前
设备预测性维护算法核心功能有哪些?六大模块拆解智能运维的“技术骨架”
运维·算法
期末考复习中,蓝桥杯都没时间学了20 分钟前
力扣刷题13
数据结构·算法·leetcode
2201_7569890930 分钟前
C++中的事件驱动编程
开发语言·c++·算法
会飞的战斗鸡36 分钟前
JS中的链表(含leetcode例题)
javascript·leetcode·链表
多米Domi01141 分钟前
0x3f 第48天 面向实习的八股背诵第五天 + 堆一题 背了JUC的题,java.util.Concurrency
开发语言·数据结构·python·算法·leetcode·面试
2301_8223776542 分钟前
模板元编程调试方法
开发语言·c++·算法
故以往之不谏1 小时前
函数--值传递
开发语言·数据结构·c++·算法·学习方法
渐暖°1 小时前
【leetcode算法从入门到精通】5. 最长回文子串
vscode·算法·leetcode