ACM 选手图解 LeetCode 二叉树的所有路径

简介: ACM 选手图解 LeetCode 二叉树的所有路径

大家好呀,我是帅蛋。


今天解决二叉树的所有路径,是考察二叉树的经典好题。


还是那句话,二叉树的题目,做多了你就会发现,题目之间都存在相似性,解题是有固定的套路。


闲话少说,下面让我们一起来看题吧!

640.png


   LeetCode 257:二叉树的所有路径



题意


给定一个二叉树的根节点 root,按任意顺序,返回所有从根节点到叶子节点的路径。


示例


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

输出:["1->2->5", "1->3"]

640.png


提示


叶子节点是指没有子节点的节点。


  • 树中节点的数目在范围 [1, 100] 内
  • -100 <= Node.val <= 100

题目解析


难度简单,是考察二叉树前序遍历的经典好题。


咦,我怎么直接把解法说出来了~


如果蛋粉们看过我之前【ACM 选手图解 LeetCode 二叉树的最大深度】的文章,应该会发现这题与其中的一个常规解法相似,我叫这种解法为:自顶向下


类似在哪呢?就是方向上一致,维护的顺序一致:


在【二叉树的最大深度】中,自顶向下的解法是从根节点递归到叶子节点,计算这一条路径上的深度,并更新维护最大深度。每次先维护根节点的深度,再递归左子树、右子树。


而在本题二叉树的所有路径中,自顶向下的解法也是从从根节点递归到叶子节点,只不过这次不是计算路径上的深度,而是记录路径上的节点,并更新维护路径。每次都是先从根节点开始,先递归左子树,再递归右子树。


你看,这种这种每次都是先根节点,再是左子树,最后右子树,不就是前序遍历的方式嘛。


640.png



递归法


既然是用递归法,那还是按照往常,祭出递归二步曲:


(1) 找出重复的子问题。


前序遍历的顺序是:根节点、左子树、右子树。


在本题同样也是这个顺序:将根节点加入路径,递归左子树,递归右子树


对于左子树和右子树来说,也都是同样的操作。


self.getPaths(root.left, path, res)
self.getPaths(root.right, path, res)


(2) 确定终止条件。


对于二叉树的所有路径中的每条路径,当遍历到叶子节点的时候为当前路径的结束。并且将当前路径加入结果集。

# 如果当前节点是叶子节点,将当前路径加入结果集
if root.left == None and root.right == None:
  res.append(path)


最重要的两点确定完了,那总的代码也就出来了。


Python 代码实现

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def getPaths(self, root, path, res):
        if not root:
            return
        # 节点值加入当前路径
        path += str(root.val)
        # 如果当前节点是叶子节点,将当前路径加入结果集
        if root.left == None and root.right == None:
            res.append(path)
        # 如果当前节点不是叶子节点,继续遍历左子树和右子树。
        else:
            path += '->'
            self.getPaths(root.left, path, res)
            self.getPaths(root.right, path, res)
    def binaryTreePaths(self, root: Optional[TreeNode]) -> List[str]:
        res = []
        self.getPaths(root, '', res)
        return res


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 void getPaths(TreeNode root, String path, List<String>res){
        if (root == null){
            return ;
        }
        // 节点值加入当前路径
        StringBuffer onepath = new StringBuffer(path);
        onepath.append(Integer.toString(root.val));
        // 如果当前节点是叶子节点,将当前路径加入结果集
        if (root.left == null && root.right == null){
            res.add(onepath.toString());
        }
        // 如果当前节点不是叶子节点,继续遍历左子树和右子树
        else{
            onepath.append("->");
            getPaths(root.left, onepath.toString(), res);
            getPaths(root.right, onepath.toString(), res);
        }
    }
    public List<String> binaryTreePaths(TreeNode root) {
        List<String> res = new ArrayList<String>();
        getPaths(root, "", res);
        return res;
    }
}



本题解,每个节点都会被访问到,所以时间复杂度为 O(n)


此外在递归过程中调用了额外的栈空间,栈的大小取决于二叉树的高度,二叉树最坏情况下的高度为 n,所以空间复杂度为 O(n)


非递归法(迭代)


非递归的做法,很多人又叫迭代法迭代法就是不断地将旧的变量值,递推计算新的变量值


迭代法是用栈来做,对二叉树前序遍历迭代法不太了解的蛋粉可以看下面这篇文章:


ACM 选手带你玩转二叉树前中后序遍历(非递归版)


当然,因为这道题还要记录路径,那还得需要一个额外的栈来存储路径


根据栈“先入后出”的特点,结合前序遍历的顺序,迭代的过程也就出来了:


每次都是先将根节点放入栈,然后右子树,最后左子树。


具体步骤如下所示:


  • 初始化维护两个栈:一个栈是递归栈,将根节点入栈,另一个栈是路径栈,将根节点的值入栈。
  • 当栈不为空时,弹出两个栈的栈顶元素:
  • 若 node 为叶子节点,将路径加入结果集。
  • 若 node 的右子树不为空,右孩子及右孩子的值分别入递归栈和路径栈。
  • 若 node 的左子树不为空,左孩子及左孩子的值分别入递归栈和路径栈。


以下图为例:


640.png

首先初始化递归栈 stack、路径栈 stackPath 和结果集 res。

640.png

# 初始化递归栈、路径栈和结果集
stack = [root]
stackPath = []
res = []
stackPath.append(str(root.val))

当栈不为空,递归栈栈顶元素 node 出栈,路径栈的栈顶元素 path 出栈。

640.png


# 节点和路径出栈
node = stack.pop()
path = stackPath.pop()


此时 node 节点为 1,有左子树和右子树,此时先右子树入栈,再左子树入栈。

640.png



# 右子树入栈
if node.right:
    stack.append(node.right)
    stackPath.append(path + "->" + str(node.right.val))
# 左子树入栈
if node.left:
    stack.append(node.left)
    stackPath.append(path + "->" + str(node.left.val))


接下来继续重复上面的操作。


弹出栈顶元素 node = 2,path = “1->2”,此时 node = 2 有右子树,右子树入栈。

640.png


弹出栈顶元素 node = 5,path = “1->2->5”,此时 node = 5 为叶子节点,将 path 加入结果集 res。

640.png


# 如果当前节点为叶子节点
if node.left == None and node.right == None:
    res.append(path)


弹出栈顶元素 node = 3,path = “1->3”,此时 node = 3 为叶子节点,同样将 path 加入结果集 res。

640.png


当栈为空时,结束,返回此时的 res 结果集。


Python 代码实现

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def binaryTreePaths(self, root: Optional[TreeNode]) -> List[str]:
        if not root:
            return []
        # 初始化递归栈、路径栈和结果集
        stack = [root]
        stackPath = []
        res = []
        stackPath.append(str(root.val))
        while stack:
            # 节点和路径出栈
            node = stack.pop()
            path = stackPath.pop()
            # 如果当前节点为叶子节点
            if node.left == None and node.right == None:
                res.append(path)
            # 右子树入栈
            if node.right:
                stack.append(node.right)
                stackPath.append(path + "->" + str(node.right.val))
            # 左子树入栈
            if node.left:
                stack.append(node.left)
                stackPath.append(path + "->" + str(node.left.val))
        return res


Java 代码实现


特别提醒:Java 可以直接定义一个成员变量为object的栈,这个栈可以放 TreeNode,可以放 String,这样就不需要搞两个栈,都放在一个栈里即可。

/**
 * 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<String> binaryTreePaths(TreeNode root) {
        // 初始化
        List<String> res = new ArrayList<>();
        if (root == null)
            return res;
        Stack<Object> stack = new Stack<>();
        stack.push(root);
        stack.push(root.val + "");
        while (!stack.isEmpty()) {
            // 节点和路径出栈
            String path = (String) stack.pop();
            TreeNode node = (TreeNode) stack.pop();
            // 如果当前节点为叶子节点
            if (node.left == null && node.right == null) {
                res.add(path);
            }
            // 右子树入栈
            if (node.right != null) {
                stack.push(node.right);
                stack.push(path + "->" + node.right.val);
            }
            // 左子树入栈
            if (node.left != null) {
                stack.push(node.left);
                stack.push(path + "->" + node.left.val);
            }
        }
        return res;
    }
}


同样,本题解时间复杂度为 O(n)空间复杂度为 O(n)



求解二叉树的所有路径到这就结束辣,是不是觉得自己的功力更上一层楼了?


求个路径也能和前序遍历扯上关系,你看吧,题目的解决都是从我们过去学过的知识中寻找办法


教给你这么有意思的二叉树,不得赶紧给我来个点赞 + 在看 + 转发大礼包?


哈哈哈哈,必须得有是吧~


我是帅蛋,我们下次见!

相关文章
|
1月前
【LeetCode 35】112.路径总和
【LeetCode 35】112.路径总和
23 0
|
1月前
【LeetCode 36】113.路径总和II
【LeetCode 36】113.路径总和II
28 0
|
1月前
【LeetCode 31】104.二叉树的最大深度
【LeetCode 31】104.二叉树的最大深度
19 2
|
1月前
【LeetCode 29】226.反转二叉树
【LeetCode 29】226.反转二叉树
15 2
|
1月前
【LeetCode 43】236.二叉树的最近公共祖先
【LeetCode 43】236.二叉树的最近公共祖先
18 0
|
1月前
【LeetCode 38】617.合并二叉树
【LeetCode 38】617.合并二叉树
14 0
|
1月前
【LeetCode 37】106.从中序与后序遍历构造二叉树
【LeetCode 37】106.从中序与后序遍历构造二叉树
16 0
|
1月前
【LeetCode 34】257.二叉树的所有路径
【LeetCode 34】257.二叉树的所有路径
12 0
|
1月前
【LeetCode 32】111.二叉树的最小深度
【LeetCode 32】111.二叉树的最小深度
16 0
|
2月前
|
Unix Shell Linux
LeetCode刷题 Shell编程四则 | 194. 转置文件 192. 统计词频 193. 有效电话号码 195. 第十行
本文提供了几个Linux shell脚本编程问题的解决方案,包括转置文件内容、统计词频、验证有效电话号码和提取文件的第十行,每个问题都给出了至少一种实现方法。
LeetCode刷题 Shell编程四则 | 194. 转置文件 192. 统计词频 193. 有效电话号码 195. 第十行