NC9 二叉树中是否存在节点和为指定值的路径

题目描述

给定一个二叉树和一个值 sum,判断是否有从根节点到叶子节点的节点值之和等于 sum 的路径,

方法一: 暴力递归

解题思路

因为题目中所说的路径是从根节点到叶子节点所有节点走完,而二叉树里可以用前序遍历、中序遍历、后序遍历从根节点往下一条一条地遍历到叶子节点。

我们需要做的就是在这遍历的过程中,把每个节点的值加起来,到叶子节点的时候,判断这些加起来的值是不是等于指定值就可以了。如果相等,那么我们就找到了存在这样的一条路劲;如果不相等,则不存在。

这里就以前序遍历这个方式进行说明:所有我们自己的操作都在进行左右子树遍历之前进行,即相加操作的代码都在遍历左右子树之前书写。具体思路参考下面提供的代码。

回顾整个操作,大体的思路就是找到所有的路径,再挨个判断每个路径是否符合条件,不符合就继续玩下找,直到找完了;符合就直接返回即可,无需再往下找。

以下是 targetSum 为 9 时的演示示例:
图片说明

复杂度

时间复杂度:O(N)

  • 递归遍历每个节点,每个节点都需要遍历访问到,总共右N个节点,时间复杂度即为O(N)。

空间复杂度:O(N)

  • 递归需要用到栈结构,开辟的栈空间和树的高度有关,即O(h), h 为该二叉树的高度,即O(logN);但最坏的情况下,可以把每个节点都压入栈中,即空间复杂度可以达到 O(N)。

示例代码

public class Solution {
    /**
     * @param root TreeNode类 
     * @param sum int整型 
     * @return bool布尔型
     */
    public boolean hasPathSum (TreeNode root, int sum) {
        // 判断特殊情况
        if (root == null) {
            return false;
        }

        return preOrder(root, root.val, sum);
    }


    /**
     * @param root 当前走到的节点,TreeNode类 
     * @param curSum 走到当前节点时的“节点和” 
     * @param targetSum “节点和”的指定值
     * @return 
     */
    public boolean preOrder(TreeNode root, int curSum, int targetSum){
        // 到达叶子节点,也就是有一条路径,之后再判断这个路径符不符合条件
        if (root.left == null && root.right == null) {
            // 存在节点和为指定值,符合条件
            if (curSum == targetSum) {
                return true;
            }
            return false;
        }

        // 递归进去防止空指针异常,加个判断
        if (root.left != null) {
            // 遍历左子树,看左子树有没有这样的路径
            boolean b1 = preOrder(root.left, curSum+root.left.val, targetSum);
            // 找到这样的路径,直接返回 true
            if (b1) {
                return true;
            }            
        }
        if (root.right != null) {
            // 遍历右子树,看右子树有没有这样的路径
            boolean b2 = preOrder(root.right, curSum+root.right.val,targetSum);
            if (b2) {
                return true;
            }            
        }

        // 左右子树都没有这样的路径,直接返回 false 不存在
        return false;
    }


}

方法二: 用栈

解题思路

之前用的是二叉树的递归遍历形式找路径。 我们知道用栈结构可以将递归方式改写为非递归方式,所以也可以使用栈结构来解决该题。

对于先序遍历,非递归的方式就是:先用栈保存头节点,因为栈结构的特性是后进先出,所以我们先压右节点,再压左节点,这样到后面弹出就是先弹左节点,再弹右节点。对于先序遍历是如此,但此题只要求我们找路径,所以先遍历哪个子树的顺序并不是关键点。

关键的地方仍然在于,在遍历的过程中,记录下每个节点的路径和,到叶子节点的时候,就判断此时的路径和是不是目标值即可。

以下是 targetSum 为 9 时的演示示例:
图片说明

具体实现方式请参考代码。

复杂度

较方法一而言并没有优化多少,只是使用的栈结构是我们自己定义的而已。
时间复杂度:O(N)

  • 每个节点都需要挨个访问左右节点,共有N个节点需要遍历访问到,即时间复杂度为O(N)。

空间复杂度:O(N)

  • 极端情况下,一开始一直往左节点访问,它的所有右节点存储到栈空间中,此时栈空间达到最大,之后会从栈里弹出元素,所以开辟的栈空间和该二叉树的高度有关,即空间复杂度为O(h),h为该二叉树的高度,即O(logN);
    但最坏的情况下,可以把每个节点都压入栈中,即空间复杂度可以达到 O(N)。

示例代码

public class Solution {

    // 保存 节点 和 节点对应的路径和 的自定义类
    class Node{
        TreeNode node; // 节点
        int sum;       // 节点对应的路径和

        public Node(TreeNode node, int sum){
            this.node = node;
            this.sum = sum;
        }
    }

    /**
     * 
     * @param root TreeNode类 
     * @param sum int整型 
     * @return bool布尔型
     */
    public boolean hasPathSum (TreeNode root, int sum) {
        // 判断特殊情况
        if (root == null) {
            return false;
        }


        // 用栈解决该问题, 栈里装的是自定义的 Node 类。
        Stack<Node> stack = new Stack<>();
        // 先压头节点
        stack.push(new Node(root, root.val));
        while (!stack.isEmpty()) {
            Node popNode = stack.pop();
           TreeNode node = popNode.node;

            // 此时弹出的是叶子节点
            if (node.left == null && node.right == null) {
                // 路径和是否等于指定值
                if (popNode.sum == sum) {
                    return true;
                }  
            }

            // 如果右孩子不为空,就压右孩子
            if (node.right != null) {
                // 压节点的时候, 把对应节点的路径和也记录上
                stack.push( new Node(node.right, popNode.sum + node.right.val));
            }

            // 如果左孩子不为空,就压左孩子
            if (node.left != null) {
                // 压节点的时候, 把对应节点的路径和也记录上
                stack.push( new Node(node.left, popNode.sum + node.left.val));
            }
        }

        // 整个遍历过程结束,还没有找到符合条件的路径,就返回 false
        return false;
    } 

}