(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]。
相关推荐
会员源码网9 小时前
使用`mysql_*`废弃函数(PHP7+完全移除,导致代码无法运行)
后端·算法
木心月转码ing10 小时前
Hot100-Day10-T438T438找到字符串中所有字母异位词
算法
HelloReader11 小时前
Wi-Fi CSI 感知技术用无线信号“看见“室内的人
算法
颜酱13 小时前
二叉树分解问题思路解题模式
javascript·后端·算法
qianpeng89715 小时前
水声匹配场定位原理及实验
算法
董董灿是个攻城狮1 天前
AI视觉连载8:传统 CV 之边缘检测
算法
AI软著研究员1 天前
程序员必看:软著不是“面子工程”,是代码的“法律保险”
算法
FunnySaltyFish1 天前
什么?Compose 把 GapBuffer 换成了 LinkBuffer?
算法·kotlin·android jetpack
颜酱1 天前
理解二叉树最近公共祖先(LCA):从基础到变种解析
javascript·后端·算法
地平线开发者2 天前
SparseDrive 模型导出与性能优化实战
算法·自动驾驶