【算法题解】43. 二叉树的后序遍历

这是一道 简单

https://leetcode.cn/problems/binary-tree-postorder-traversal/

题目

给你一棵二叉树的根节点 root ,返回其节点值的 后序遍历

示例 1:

输入:root = [1,null,2,3]
输出:[1,3,2]

示例 2:

输入:root = []
输出:[]

示例 3:

输入:root = [1]
输出:[1]

提示:

  • 树中节点数目在范围 [ 0 , 100 ] [0, 100] [0,100]
  • − 100 < = N o d e . v a l < = 100 -100 <= Node.val <= 100 100<=Node.val<=100

进阶:递归算法很简单,你可以通过迭代算法完成吗?


递归解法

不管是 二叉树的前序遍历
二叉树的中序遍历
还是本题要求的后序,递归是最简单也是最基本的实现。

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> postorderTraversal(TreeNode root) {
        List<Integer> ans = new ArrayList<>();
        dfs(root, ans);
        return ans;
    }

    private void dfs(TreeNode node, List<Integer> ans){
        if(node == null){
            return;
        }
        dfs(node.left, ans);
        dfs(node.right, ans);
        ans.add(node.val);
    }
}
Go 代码实现
/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
func postorderTraversal(root *TreeNode) (ans []int) {
    var dfs func(node *TreeNode)

    dfs = func(node *TreeNode){
        if node == nil {
            return
        }
        dfs(node.Left)
        dfs(node.Right)
        ans = append(ans, node.Val)
    }

    dfs(root)

    return ans
}
复杂度分析

时间复杂度: O ( N ) O(N) O(N)N 为二叉树中的节点个数,每个节点都需要访问一次,总计 N 次。

空间复杂度: O ( N ) O(N) O(N)N 为二叉树中的节点个数,空间复杂度取决于递归调用栈的深度,最大为 N


迭代解法

后序遍历的顺序为 左 -> 右 -> 根,只要还有左节点,就一直往左边找下去。
image.png

翻译成代码实现,以 Java 为例,stack 代表栈,用于记录走过的路径。

while(root != null){
    stack.push(root);
    root = root.left;
}

当走到最左边的节点(如节点4)入栈后,因为其已经没有左节点了,所以可以出栈并记录答案。
image.png

根据入栈顺序,下一个出栈的应该是 节点2 , 但是此时我们并不能记录 节点2 的值,因为他还有右节点,根据后序遍历的顺序(左 -> 右 -> 根),此时我们应该先遍历其右子树。那么此时 节点2 就不应该出栈,但是刚才 节点4 是直接出栈的,处理方式有冲突,怎么办呢?

我们可以注意到,节点4 没有右节点,而 节点2 有右节点,所以我们根据他的右节点是否为空来判断一下,为空就弹出栈并记录答案。不为空就不弹出,而是继续把他的右子树根据规则入栈。
image.png

还是以 Java 代码为例,其中 ans 是用于记录答案的列表。

TreeNode top = stack.peek();
TreeNode root = top.right;
// 当右节点为空,记录答案,并出栈。
if(root == null){
    ans.add(top.val);
    stack.pop();
}else{
    // 当右节点不空,根据规则,继续入栈。
    while(root != null){
    	stack.push(root);
        root = root.left;
    }
}

节点5 出栈规则和左边节点是一样的,只要其没有右节点,就可以直接出栈并记录答案。
image.png

接下来是比较关键的一点,当 节点2 出栈的时候,根据上面的规则会发现其包含右节点(节点5), 需要将 节点5 重新入栈,但是 节点5 刚才已经计算过了,怎么办?

最最简单的办法就是把所有记录过的节点都存起来,遇到记录过的就不管了。如计算 节点2 的时候,发现其 右节点5 已经计算过了,那么 节点2 就可以出栈并计入答案了。

其实还有一个更简单的办法,我们不需要把所有记录过的节点都存起来。

我们只需要知道上一个出栈的节点就可以了,因为根据规则(左 -> 右 -> 根),如果某个节点包含右节点,那么上一个出栈的肯定是他的右节点

那么出栈规则就演化为:只要其右节点为空或者上一个出栈的就是其右节点,那么这个根节点就也可以出栈并计入答案了。

TreeNode top = stack.peek();
TreeNode root = top.right;
if(root == null || root == pre){
    ans.add(top.val);
    stack.pop();
    pre = top;
}

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> postorderTraversal(TreeNode root) {
        List<Integer> ans = new ArrayList<>();
        Deque<TreeNode> stack = new LinkedList<>();

        while(root != null){
            stack.push(root);
            root = root.left;
        }

        TreeNode pre = null;
        while(!stack.isEmpty()){
            TreeNode top = stack.peek();
            root = top.right;
            if(root == null || root == pre){
                ans.add(top.val);
                stack.pop();
                pre = top;
            }else{
                while(root != null){
                    stack.push(root);
                    root = root.left;
                }
            }
        }
        return ans;
    }

}
Go 代码实现
/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
func postorderTraversal(root *TreeNode) (ans []int) {

    stack := []*TreeNode{}

    for root != nil {
        stack = append(stack, root)
        root = root.Left
    }

    var pre *TreeNode

    for len(stack) > 0 {
        top := stack[len(stack) - 1]
        root = top.Right
        if root == nil || root == pre {
            ans = append(ans, top.Val)
            stack = stack[:len(stack) - 1]
            pre = top
        }else {
            for root != nil {
                stack = append(stack, root)
                root = root.Left
            }
        }
    }

    return ans
}

复杂度分析

时间复杂度: O ( N ) O(N) O(N)N 为二叉树中的节点个数,每个节点都需要访问一次,总计 N 次。

空间复杂度: O ( N ) O(N) O(N)N 为二叉树中的节点个数,空间复杂度取决于调用栈的深度,最大为 N

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

i余数

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值