36.二叉树的中序遍历

73 阅读2分钟

题目链接

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

解法1 递归

思路

首先要搞清楚什么是中序遍历,其实什么序就是根节点在哪里,中序就是根节点在中间,按照左中右的顺序来遍历整颗树。

所以递归的时候按照左中右的顺序即可。

代码

function inorderTraversal(root: TreeNode | null): number[] {
    if (!root) {
        return [];
    }
    const result = [];

    const dfs = (node) => {
        if (!node) return;
        dfs(node.left);
        result.push(node.val);
        dfs(node.right);
    }
    dfs(root);
    return result;
};

时空复杂度

时间复杂度:遍历所有节点 O(n)

空间复杂度:递归会有栈开销,最差是只有左节点,所以是 O(n)

解法2 迭代

思路

迭代的话需要一个当前指针,和保存前序节点的栈。

一路走到最左边并将当前节点入栈,达到最左边之后弹出栈顶,将该节点的值放入结果集合当中。接下来转向该节点的右子树,直到循环结束。结束条件是当前节点为 null 或者 栈为空。

代码

function inorderTraversal(root: TreeNode | null): number[] {
    if (!root) {
        return [];
    }
    const result = [];
    const stack = [];
    let current = root;
    
    while (current || stack.length > 0) {
        while (current) {
            stack.push(current);
            current = current.left;
        }

        current = stack.pop();
        result.push(current.val);

        current = current.right;
    }

    return result;
};

时空复杂度

时间复杂度:遍历所有节点 O(n)

空间复杂度:显示栈开销 O(n)

解法3 最优解法Morris中序遍历

代码

function inorderTraversal(root: TreeNode | null): number[] {
    const result: number[] = [];
    let current = root;

    while (current !== null) {
        if (current.left === null) {
            // 当前节点没有左子树,直接访问它
            result.push(current.val);
            // 然后往右边走
            current = current.right;
        } else {
            // 找左子树中最右的节点(中序前驱)
            let predecessor = current.left;
            while (predecessor.right !== null && predecessor.right !== current) {
                // 一直往右,直到找到最右边的那个节点
                predecessor = predecessor.right;
            }

            if (predecessor.right === null) {
                // 第一次到达当前节点,在前驱节点建立一个指向当前节点的临时链接(穿线)
                predecessor.right = current;
                // 然后继续往左子树走
                current = current.left;
            } else {
                // 第二次到达当前节点,说明左子树已经访问过了
                // 把之前建立的链接断掉(恢复树结构)
                predecessor.right = null;

                // 访问当前节点
                result.push(current.val);

                // 转向右子树
                current = current.right;
            }
        }
    }

    return result;
};

时间复杂度:遍历所有节点 O(n)

空间复杂度:没有额外开销 O(1)