LeetCode刷题之树(递归)

1,074 阅读20分钟

递归

  • 树是一种递归结构,所以树的问题很多都可以用递归来解决
  • 我们用递归解决问题的时候,各个不同的递归分支就可以看成是树的分支,所以可以将递归问题抽象成树的问题
  • 递归解法需要明确三个点
    1. 返回值
    2. 调用单元做了什么
    3. 终止条件(base case)

104. 二叉树的最大深度(Easy)

给定一个二叉树,找出其最大深度。二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。

说明: 叶子节点是指没有子节点的节点。

示例:

给定二叉树 [3,9,20,null,null,15,7],  

     3
    / \
  9    20
      /  \
     15   7
返回它的最大深度 3 。

解法一:可以使用dfs来解决,明确三个点:

  1. 返回值:统计出的最大深度
  2. 调用单元做了什么:当前深度=左右子树的最大深度+1
  3. 终止条件:递归到空节点
class Solution {
    public int maxDepth(TreeNode root) {
        //base case
        if (root == null){
            return 0;
        }

        //当前深度=左右子树的最大深度+1
        return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
    }
}

解法二:除了使用dfs来求最大深度之外,还可以对二叉树进行“按层遍历”(BFS),统计出二叉树的层数,即为最大深度

  • 对二叉树进行从左到右的按层遍历,准备一个队列queue来存储节点
  • 先把根节点存储进去,弹出,层数+1,然后将其左右节点存储进去
  • 重复上述过程直到queue为空
class Solution {
    public int maxDepth(TreeNode root) {
        if (root == null) {
            return 0;
        }
        //BFS的层次遍历思想,记录二叉树的层数,
        //遍历完,层数即为最大深度
        LinkedList<TreeNode> queue = new LinkedList<>();
        queue.add(root);
        int maxDepth = 0;
        while (!queue.isEmpty()) {
            maxDepth++;
            int levelSize = queue.size();
            for (int i = 0; i < levelSize; i++) {
                TreeNode node = queue.pollFirst();
                if (node.left != null) {
                    queue.add(node.left);
                }
                if (node.right != null) {
                    queue.add(node.right);
                }
            }
        }
        return maxDepth;
    }
}


110. 平衡二叉树

给定一个二叉树,判断它是否是高度平衡的二叉树。本题中,一棵高度平衡二叉树定义为:一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。

示例 1:

给定二叉树 [3,9,20,null,null,15,7]  

    3
   / \
  9  20
    /  \
   15   7
返回 true

示例 2:

给定二叉树 [1,2,2,3,3,null,null,4,4]

       1
      / \
     2   2
    / \
   3   3
  / \
 4   4
返回 false

解法一:这道题和上道题有点联系,本题是判断一棵二叉树是否为平衡,而判断平衡的条件需要用到深度,所以可以遍历二叉树,用上道题的解法求出根节点左右子树的最大深度进行比较

class Solution {
    public boolean isBalanced(TreeNode root) {
        //base case
        if (root == null){
            return true;
        }

        //判断当前节点所在的二叉树是否平衡
        if (Math.abs(maxDepth(root.left) - maxDepth(root.right)) > 1){
            return false;
        }

        //判断当前节点的左右子树是否平衡
        return isBalanced(root.left) && isBalanced(root.right);
    }
}

解法二:上述解法存在着大量的重复计算过程,比如判断根节点是否平衡的时候,已经计算过左右子树了,但是后面又重复判断了左右子树是否平衡。

  • 方法是如果我们发现子树不平衡,则不计算具体的深度,而是直接返回-1
  • 那么优化后的方法为:对于每一个节点,我们通过checkDepth方法递归获得左右子树的深度,如果子树是平衡的,则返回真实的深度,若不平衡,直接返回-1
class Solution {
    public boolean isBalanced(TreeNode root) {
        if (checkDepth(root) == -1){
            return false;
        }else {
            return true;
        }
    }

    private int checkDepth(TreeNode root){
        //base case:到达空节点,直接返回0
        if (root == null){
            return 0;
        }
        
        //调用单元
        //求出左右子树的最大深度
        int left = checkDepth(root.left);
        //如果其中一个不平衡,那么整个不平衡,直接返回-1
        if (left == -1){
            return -1;
        }
        int right = checkDepth(root.right);
        if (right == -1){
            return -1;
        }
        
        //比较当前节点的左右子树最大深度差
        int diff = Math.abs(left - right);
        //不平衡的话直接返回-1
        if (diff > 1){
            return -1;
        }else {
            //平衡就返回深度
            return 1 + Math.max(left, right);
        }
    }
}


543. 二叉树的直径(Easy)

给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过根结点。

示例 :

给定二叉树

          1
         / \
        2   3
       / \     
      4   5

返回 3, 它的长度是路径 [4,2,1,3] 或者 [5,2,1,3]。

注意:两结点之间的路径长度是以它们之间边的数目表示。


题解:这道题让我们求最大直径长度,换个说法就是求节点的左右子树深度之和,要找出最长的,只需要遍历每个节点即可

class Solution {
    private int max = 0;
    public int diameterOfBinaryTree(TreeNode root) {
        depth(root);
        return max;
    }

    private int depth(TreeNode root) {
        //base case
        if (root == null) {
            return 0;
        }
        //求出左右子树的深度
        int rightDepth = depth(root.right);
        int leftDepth = depth(root.left);
        //比较原有max 和 节点左右子树深度之和
        max = Math.max((rightDepth + leftDepth), max);
        //返回当前节点的深度
        return Math.max(leftDepth, rightDepth) + 1;
    }
}


226. 翻转二叉树(Easy)

翻转一棵二叉树。

示例:

输入:

     4
   /   \
  2     7
 / \   / \
1   3 6   9
输出:

     4
   /   \
  7     2
 / \   / \
9   6 3   1

递归解法:递归的三个关键点

  1. 返回值:交换之后的根节点
  2. 调用单元做了什么:交换 已经交换成功的左右节点
  3. 终止条件:节点为空
class Solution {
    public TreeNode invertTree(TreeNode root) {
        //base case
        if (root == null){
            return null;
        }
        //将左节点先保存起来
        TreeNode left = root.left;
        //交换左右节点
        root.left = invertTree(root.right);
        root.right = invertTree(left);
        //返回根节点
        return root;
    }
}

非递归解法:非递归的方法也不复杂,跟二叉树的层序遍历一样,需要用queue来辅助,先把根节点排入队列中,然后从队中取出来,交换其左右节点,如果存在则分别将左右节点在排入队列中,以此类推直到队列中木有节点了停止循环,返回root即可。

class Solution {
    public TreeNode invertTree(TreeNode root) {
        if (root == null) {
            return null;
        }
        //准备一个队列
        Queue<TreeNode> queue = new LinkedList<>();
        //先将根节点存储进去
        ((LinkedList<TreeNode>) queue).push(root);
        
        //交换左右节点
        while (!queue.isEmpty()) {
            TreeNode node = ((LinkedList<TreeNode>) queue).pop();
            TreeNode tmp = node.left;
            //交换左右节点
            node.left = node.right;
            node.right = tmp;
            //如果左右节点不为空,添加进队列
            if (node.left != null){
                ((LinkedList<TreeNode>) queue).push(node.left);
            }
            if (node.right != null){
                ((LinkedList<TreeNode>) queue).push(node.right);
            }
        }
        return root;
    }
}


617. 合并二叉树(Easy)

给定两个二叉树,想象当你将它们中的一个覆盖到另一个上时,两个二叉树的一些节点便会重叠。

你需要将他们合并为一个新的二叉树。合并的规则是如果两个节点重叠,那么将他们的值相加作为节点合并后的新值,否则不为 NULL 的节点将直接作为新二叉树的节点。

示例 1:
输入: 
	Tree 1                     Tree 2                  
          1                         2                             
         / \                       / \                            
        3   2                     1   3                        
       /                           \   \                      
      5                             4   7                  
输出: 
合并后的树:
	     3
	    / \
	   4   5
	  / \   \ 
	 5   4   7
注意: 合并必须从两个树的根节点开始。

题解:这道题依然是采用递归解法,先明确三个点

  1. 返回值:归并之后的节点
  2. 调用单元做了什么:将两节点合并
    • 如果只有左节点存在,返回左节点,反之返回右节点
    • 都存在就用新节点存储两节点和
  3. 终止条件:两节点都为空
class Solution {
    public TreeNode mergeTrees(TreeNode t1, TreeNode t2) {
        //base case
        if (t1 == null && t2 == null) {
            return null;
        }

        //只有一个存在,就返回存在的那个
        if (t1 == null) {
            return t2;
        }
        if (t2 == null) {
            return t1;
        }

        //都存在的话用新节点合并
        TreeNode node = new TreeNode(t1.val + t2.val);
        //继续合并其左右节点
        node.left = mergeTrees(t1.left, t2.left);
        node.right = mergeTrees(t1.right, t2.right);
        return node;
    }
}


112. 路径总和(Easy)

给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。

说明: 叶子节点是指没有子节点的节点。

示例:  给定如下二叉树,以及目标和 sum = 22,

              5
             / \
            4   8
           /   / \
          11  13  4
         /  \      \
        7    2      1
返回 true, 因为存在目标和为 22 的根节点到叶子节点的路径 5->4->11->2。

递归解法:这道题让我们判断是否存在一条路径的和等于目标值,很显然可以使用dfs来解决,依然看递归的3个要点

  1. 终止条件:遍历到为空就结束,因为判断的是根节点到叶子节点的和
  2. 调用单元做了什么:当前节点的值加上左节点的值或者右节点的值
  3. 返回值:从当前节点到叶子节点的路径和是否等于目标值
class Solution {
    public boolean hasPathSum(TreeNode root, int sum) {
        //base case 如果递归到空节点都不等于目标值,返回false
        if (root == null){
            return false;
        }
        
        //到叶子节点的时候路径值等于目标值,返回true
        if (root.left == null && root.right == null && sum == 0){
            return true;
        }
        
        //判断当前节点的左右节点的路径和 = 减去节点的值的目标和
        return hasPathSum(root.left, sum - root.val) || hasPathSum(root.right, sum - root.val);
    }
}

非递归解法:采用先序遍历,左右子结点都需要加上其父结点值,这样当遍历到叶结点时,如果和 sum 相等了,那么就说明一定有一条从 root 过来的路径。

class Solution {
    public boolean hasPathSum(TreeNode root, int sum) {
        if (root == null){
            return false;
        }
        
        //使用栈存储当前层的节点
        Stack<TreeNode> stack = new Stack<>();
        stack.push(root);
        
        //先序遍历二叉树
        while (!stack.isEmpty()) {
            //弹出一个节点
            TreeNode tmp = stack.pop();
            
            //如果是叶子节点,判断路径和与目标值
            if (tmp.left == null && tmp.right == null) {
                if (tmp.val == sum){
                    return true;
                }
            }
            
            //如果左右节点存在,加入栈
            if (tmp.left != null){
                tmp.left.val += tmp.val;
                stack.push(tmp.left);
            }
            //如果左右节点存在,加入栈
            if (tmp.right != null){
                tmp.right.val += tmp.val;
                stack.push(tmp.right);
            }
        }
        //遍历完都没有找到,返回false
        return false;
    }
}


437. 路径总和 III(Easy)

给定一个二叉树,它的每个结点都存放着一个整数值。 找出路径和等于给定数值的路径总数。 路径不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。

二叉树不超过1000个节点,且节点数值范围是 [-1000000,1000000] 的整数。

示例:
root = [10,5,-3,3,2,null,11,3,-2,null,1], sum = 8

      10
     /  \
    5   -3
   / \    \
  3   2   11
 / \   \
3  -2   1

返回 3。和等于 8 的路径有:

1.  5 -> 3
2.  5 -> 2 -> 1
3.  -3 -> 11

递归解法:这道题是上道题的“升级版”,需要求出所有路径和等于目标值的,而且不需要从根节点开始,也不需要到叶子节点,那么很自然地可以想到使用递归解决,我们可以先准备一个求”当前节点的路径和等于目标值的数量的函数“,然后遍历这棵二叉树即可。

class Solution {
    public int pathSum(TreeNode root, int sum) {
        if (root == null){
            return 0;
        }
        //路径条数等于以当前节点开始的条数加上左右节点的条数
        int ret = pathSumStartWithRoot(root, sum) + pathSum(root.left, sum) + pathSum(root.right, sum);
        return ret;
    }

    private int pathSumStartWithRoot(TreeNode root, int sum) {
        //如果遍历到空节点,直接返回0
        if (root == null) {
            return 0;
        }
        //统计以当前节点开始的路径条数
        int ret = 0;
        //如果当前节点值等于目标和,+1
        if (root.val == sum){
            ret++;
        }
        //往下寻找
        ret += pathSumStartWithRoot(root.left, sum - root.val) + pathSumStartWithRoot(root.right, sum - root.val);
        return ret;
    }
}


572. 另一个树的子树(Easy)

给定两个非空二叉树 s 和 t,检验 s 中是否包含和 t 具有相同结构和节点值的子树。s 的一个子树包括 s 的一个节点和这个节点的所有子孙。s 也可以看做它自身的一棵子树。

示例 1:

给定的树 s:
     3
    / \
   4   5
  / \
 1   2
 
给定的树 t:
   4 
  / \
 1   2
 
返回 true,因为 t 与 s 的一个子树拥有相同的结构和节点值。
示例 2:

给定的树 s:
     3
    / \
   4   5
  / \
 1   2
    /
   0
   
给定的树 t:
   4
  / \
 1   2
 
返回 false

题解:这道题让我们判断一棵树是否为另外一棵树的子树,子树必须是从某个节点开始,然后到叶子节点的,那么就可以转为比较两棵树是否相同的问题了,遍历其中一棵树s,判断以某个节点开始的树是否等于另外一棵树t。跟上道题一样是双递归函数的解法

class Solution {
    public boolean isSubtree(TreeNode s, TreeNode t) {
        if (s == null){
            return false;
        }
        //判断以当前节点开始的二叉树与t是否相同,以及其左右节点开始的是否相同
        return isSubtreeWithRoot(s, t) || isSubtree(s.left, t) || isSubtree(s.right, t);
    }

    private boolean isSubtreeWithRoot(TreeNode s, TreeNode t) {
        //两个都为空,直接返回true
        if (t == null && s == null) {
            return true;
        }
        //其中一个为空,返回false
        if (t == null || s == null){
            return false;
        }

        //比较当前节点是否相同
        if (s.val != t.val) {
            return false;
        }
        //比较其左右子树是否相同
        return isSubtreeWithRoot(s.left, t.left) && isSubtreeWithRoot(s.right, t.right);
    }
}


101. 对称二叉树(Easy)

给定一个二叉树,检查它是否是镜像对称的。

例如,二叉树 [1,2,2,3,4,4,3] 是对称的。
    1
   / \
  2   2
 / \ / \
3  4 4  3

但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的:
    1
   / \
  2   2
   \   \
   3    3

说明:如果你可以运用递归和迭代两种方法解决这个问题,会很加分。


递归解法:

  • 这道题让我们判断一棵树是否镜像对称:即根节点的左子树和右子树是否镜像对称
  • 和上道题很类似,上道题是判断是否为子树:即一棵树的子树和另外一课树是否相等
  • 我们可以递归比较根节点的左子树和右子树是否镜像“相同”
    • 终止条件:两节点都为空,返回true
    • 调用单元做了什么:比较两树对称节点的值是否相等
    • 返回值:镜像对称的树的根节点
class Solution {
    public boolean isSymmetric(TreeNode root) {
        if (root == null) {
            return true;
        }
        //比较左右子树是否镜像对称
        return isSymmetric(root.left, root.right);
    }

    private boolean isSymmetric(TreeNode t1, TreeNode t2) {
        //base case:如果能遍历到根节点,说明镜像对称
        if (t1 == null && t2 == null) {
            return true;
        }
        //非镜像对称,或者其中一个为null,返回false
        if (t1 == null || t2 == null || t1.val != t2.val) {
            return false;
        }
        
        //比较左右子树是否镜像对称
        return isSymmetric(t1.left, t2.right) && isSymmetric(t1.right, t2.left);
    }
}

迭代解法

  • 递归解法核心思想和迭代解法是一样的,只是迭代解法需要两个队列来分别存储根节点的左子树和右子树
class Solution {
    public boolean isSymmetric(TreeNode root) {
        if (root == null) {
            return true;
        }
        
        //准备两个队列
        Queue<TreeNode> queue1 = new LinkedList<>();
        Queue<TreeNode> queue2 = new LinkedList<>();
        ((LinkedList<TreeNode>) queue1).push(root.left);
        ((LinkedList<TreeNode>) queue2).push(root.right);
        
        //遍历左子树和右子树
        while (!queue1.isEmpty() && !queue2.isEmpty()) {
            //弹出节点
            TreeNode node1 = ((LinkedList<TreeNode>) queue1).pop();
            TreeNode node2 = ((LinkedList<TreeNode>) queue2).pop();
            
            //如果两节点为空,继续
            if (node1 == null && node2 == null) {
                continue;
            }
            //其中一个为空,或者值不像等,则非镜像对称
            if (node1 == null || node2 == null || node1.val != node2.val) {
                return false;
            }
            
            //否则,将左子树的左右节点添加进queue1
            //将右子树的左右节点添加进queue2
            //注意:添加的顺序
            ((LinkedList<TreeNode>) queue1).push(node1.left);
            ((LinkedList<TreeNode>) queue1).push(node1.right);
            ((LinkedList<TreeNode>) queue2).push(node2.right);
            ((LinkedList<TreeNode>) queue2).push(node2.left);
        }
        return true;
    }
}


111. 二叉树的最小深度(Easy)

给定一个二叉树,找出其最小深度。最小深度是从根节点到最近叶子节点的最短路径上的节点数量。

说明: 叶子节点是指没有子节点的节点。

示例:
给定二叉树 [3,9,20,null,null,15,7],

    3
   / \
  9  20
    /  \
   15   7

返回它的最小深度  2.


题解:

  • 这道题让我们求最小深度,典型的DFS问题
  • 同样的找3个关键点
    • 终止条件:遍历到空,返回0
    • 调用单元做了什么:如果当前节点有一个子节点为空,那么对存在的那个节点调用递归函数,并+1,如果子节点都在,那么取小的+1返回
    • 返回值:当前节点的最小深度
class Solution {
    public int minDepth(TreeNode root) {
        if (root == null) {
            return 0;
        }
        
        //到达叶子节点,返回1
        if (root.left == null && root.right == null) {
            return 1;
        }
        
        //其中一个子节点不存在,那么最小深度=当前节点(1)+ 不为空的节点的最小深度
        int m1 = minDepth(root.right);
        int m2 = minDepth(root.left);
        if (root.left == null) {
            return 1 + m1;
        }
        if (root.right == null) {
            return 1 + m2;
        }
        
        //都不为空的话,就取小的+1
        return Math.min(m1, m2) + 1;
    }
}


404. 左叶子之和(Easy)

计算给定二叉树的所有左叶子之和。

示例:
    3
   / \
  9  20
    /  \
   15   7

在这个二叉树中,有两个左叶子,分别是 9 和 15,所以返回 24


递归解法:这道题让我们求左叶子节点的和,那么遍历二叉树,遇到为叶子节点加起来即可

class Solution {
    public int sumOfLeftLeaves(TreeNode root) {
        //base case
        if (root == null) {
            return 0;
        }
        //如果当前节点的左节点为叶子节点,将值加起来
        if (isLeaf(root.left)) {
            return root.left.val + sumOfLeftLeaves(root.right);
        }
        //返回值:当前节点的左子树的佐子结点和与右子树的左叶子节点和
        return sumOfLeftLeaves(root.left) + sumOfLeftLeaves(root.right);
    }

    private boolean isLeaf(TreeNode node) {
        if (node == null) {
            return false;
        }
        return node.left == null && node.right == null;
    }
}

迭代解法:这道题同样可以使用迭代解法解决,采用先序遍历

class Solution {
    public int sumOfLeftLeaves(TreeNode root) {
        //根节点为空,或者只有根节点,都为0
        if (root == null || (root.left == null && root.right == null)) {
            return 0;
        }
        
        int res = 0;
        //准备一个队列
        Queue<TreeNode> queue = new LinkedList<>();
        ((LinkedList<TreeNode>) queue).push(root);
        //线序遍历二叉树
        while (!queue.isEmpty()) {
            TreeNode node = ((LinkedList<TreeNode>) queue).pop();
            //如果当前节点为左叶子节点,将值加起来
            if (node.left != null && node.left.left == null && node.left.right == null) {
                res += node.left.val;
            }
            //否则,添加进队列
            if (node.left != null) {
                ((LinkedList<TreeNode>) queue).push(node.left);
            }
            if (node.right != null) {
                ((LinkedList<TreeNode>) queue).push(node.right);
            }
        }
        return res;
    }
}


687. 最长同值路径(Easy)

给定一个二叉树,找到最长的路径,这个路径中的每个节点具有相同值。 这条路径可以经过也可以不经过根节点。

注意:两个节点之间的路径长度由它们之间的边数表示。

示例 1:
输入:

              5
             / \
            4   5
           / \   \
          1   1   5
输出:
2
示例 2:
输入:

              1
             / \
            4   5
           / \   \
          4   4   5
输出:
2

注意: 给定的二叉树不超过10000个结点。 树的高度不超过1000。


题解:这道题让我们求相同节点的最长路径和,这种求路径的题目一般都可以用递归来解决,对于节点root来说,最长路径只可能是以下3种情况:1.只与左子树有关;2.只与右子树有关;3.与root有关所以返回值的定义:以该节点为终点的最长路径。老办法,先找出递归的3个关键点

  • 终止条件:遍历到空,返回0
  • 返回值:以该结点为终点的最长路径长度,最大长度保存在res
  • 调用单元做了什么:对当前节点的左右节点调用递归函数,然后看当前结点和其左右子结点之间的关系了,如果其左子结点存在且和当前节点值相同,则left自增1,否则left重置0;同理,如果其右子结点存在且和当前节点值相同,则right自增1,否则right重置0。然后用left+right来更新结果res。
class Solution {
    private int path = 0;

    public int longestUnivaluePath(TreeNode root) {
        helper(root);
        return path;
    }

    private int helper(TreeNode root) {
        //base case
        if (root == null) {
            return 0;
        }

        //求出以root.left(root.right)为终点的最长路径
        int left = helper(root.left);
        int right = helper(root.right);

        //也许包含node在内的左路径长度和右路径长度
        int leftPath = root.left != null && root.left.val == root.val ? left + 1 : 0;
        int rightPath = root.right != null && root.right.val == root.val ? right + 1 : 0;
        //path是两边加起来的长度与path比较
        path = Math.max(path, leftPath + rightPath);
        //返回值:以root为终点的最长路径
        return Math.max(leftPath, rightPath);
    }
}


337. 打家劫舍 III(Medium)

在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。

计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。

示例 1:

输入: [3,2,3,null,3,null,1]

     3
    / \
   2   3
    \   \ 
     3   1

输出: 7 
解释: 小偷一晚能够盗取的最高金额 = 3 + 3 + 1 = 7.

示例 2:

输入: [3,4,5,1,3,null,1]

     3
    / \
   4   5
  / \   \ 
 1   3   1

输出: 9
解释: 小偷一晚能够盗取的最高金额 = 4 + 5 = 9.

题解:这道题参考了labuladong大佬的解法,打家劫舍一共有三道题目,这是第三道,这一系列题目是动态规划题目,关键点在于“状态”和“选择”,这道题的状态是“索引”,选择是“抢或不抢”。

  • 终止条件:节点为空,返回0
  • 调用单元做了什么:比较抢当前节点和不抢当前节点,选出较大的值
  • 返回值:以当前节点为根节点的二叉树能抢到的最大值
class Solution {
    HashMap<TreeNode, Integer> memo = new HashMap<>();
    public int rob(TreeNode root) {
        //base case
        if (root == null) {
            return 0;
        }

        //利用备忘录消除重复子问题
        if (memo.containsKey(root)) {
            return memo.get(root);
        }
        
        //抢,接着去下下家
        int doIt = root.val 
                + (root.left == null ? 0 : rob(root.left.left) + rob(root.left.right))
                + (root.right == null ? 0 : rob(root.right.left) + rob(root.right.right));
        //不抢,然后去下家
        int notDoIt = rob(root.left) + rob(root.right);
        
        int res = Math.max(doIt, notDoIt);
        memo.put(root, res);
        return res;
    }
}


671. 二叉树中第二小的节点(Easy)

给定一个非空特殊的二叉树,每个节点都是正数,并且每个节点的子节点数量只能为 2 或 0。如果一个节点有两个子节点的话,那么这个节点的值不大于它的子节点的值。

给出这样的一个二叉树,你需要输出所有节点中的第二小的值。如果第二小的值不存在的话,输出 -1 。

示例 1:

输入: 
    2
   / \
  2   5
     / \
    5   7

输出: 5
说明: 最小的值是 2 ,第二小的值是 5 。

示例 2:

输入: 
    2
   / \
  2   2

输出: -1
说明: 最小的值是 2, 但是不存在第二小的值。

解法一:

  • 根节点是肯定存在的,一个节点要么具有 0 个或 2 个子节点,如果有子节点,那么根节点是最小的节点。
  • 直接上暴力搜索,变量first存储根节点值,second初始化为Integer最大值,开始搜索
  • 如果遇到不等于first的,肯定是比first大,更新second
class Solution {
    public int findSecondMinimumValue(TreeNode root) {
        //根节点值最小
        int first = root.val;
        int second = Integer.MAX_VALUE;
        helper(root, first, second);
        //如果没有找到,返回-1
        return (second == first || second == Integer.MAX_VALUE) ? -1 : second;
    }

    private void helper(TreeNode root, int first, int second) {
        if (root == null) {
            return;
        }
        
        //如果当前节点值不等于first且小于second,更新second
        if (root.val != first && root.val < second) {
            second = root.val;
        }
        //往下遍历
        helper(root.left, first, second);
        helper(root.right, first, second);
    }
}

解法二:一个节点要么具有 0 个或 2 个子节点,如果有子节点,那么根节点是最小的节点。

  • 如果根节点的左节点和右节点都不等于根节点,那么第二小的值就是比较小的那个
  • 如果左节点或右节点等于根节点,那么就往下递归查找
  • 如果其中一个不存在,返回存在的那个
class Solution {
    public int findSecondMinimumValue(TreeNode root) {
        //base case
        if (root == null) {
            return -1;
        }

        //左右节点为空,返回-1
        if (root.left == null && root.right == null) {
            return -1;
        }
        
        int leftVal = root.left.val;
        int rightVal = root.right.val;
        //任何一个子节点的值等于根节点,递归查找
        if (leftVal == root.val) {
            leftVal = findSecondMinimumValue(root.left);
        }
        if (rightVal == root.val) {
            rightVal = findSecondMinimumValue(root.right);
        }
        
        //左右节点都不为空且不等于根节点,返回较小值
        if (rightVal != -1 && leftVal != -1) {
            return Math.min(leftVal, rightVal);
        }
        //其中一个为空,返回不为空的
        if (leftVal != -1) {
            return leftVal;
        }
        return rightVal;
    }
}