LeetCode刷题——树(递归)

对于树,经常用的算法有递归,回溯,BFS,DFS等。下面是一些用递归算法来解的题:
104,二叉树的最大深度,easy

110,平衡二叉树,easy

543,二叉树的直径,easy

226,翻转二叉树,easy

617,合并二叉树,easy

112,路径总和,easy

113,路径总和Ⅱ,midium

572,另一个树的子树,easy

101,对称二叉树,easy

111,二叉树的最小深度,easy

404,左叶子之和,easy

687,最长同值路径,medium

671,二叉树中第二小的节点,easy

104,二叉树的最大深度,easy

给定一个二叉树,找出其最大深度。

二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。

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

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

    3
   / \
  9  20
    /  \
   15   7

返回它的最大深度 3 。

  • 代码:

    class Solution {
        public int maxDepth(TreeNode root) {
            if(root == null) return 0;
            int leftDepth = maxDepth(root.left);
            int rightDepth = maxDepth(root.right);
            return leftDepth >= rightDepth ? leftDepth + 1 : rightDepth + 1;
        }
    }
    
110,平衡二叉树,easy

给定一个二叉树,判断它是否是高度平衡的二叉树。

本题中,一棵高度平衡二叉树定义为:

一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。

示例 1:

img

输入:root = [3,9,20,null,null,15,7]
输出:true

示例 2:

img

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

示例 3:

输入:root = []
输出:true
  • 思想:先判断root 为根节点的树是不是平衡二叉树,(即比较左右子树的高度差是否不超过1),再判断以root.left 和 root.right 为根节点的树是不是平衡二叉树。

  • 代码:

    class Solution {
        public boolean isBalanced(TreeNode root) {
             if(root == null) return true;
             int l = depth(root.left);
             int r = depth(root.right);
        	 //判断当前根节点的树是否为平衡二叉树
     	    if(Math.abs(l - r) > 1) return false;
        	else 
                return (isBalanced(root.left) && isBalanced(root.right));
        }
    
        //获取整棵树的高度
        public int depth(TreeNode root){
            if(root == null) return 0;            
            return Math.max(depth(root.left),depth(root.right)) + 1;
        }
    }
    
543,二叉树的直径,easy

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

示例 :
给定二叉树

      1
     / \
    2   3
   / \     
  4   5    

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

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

  • 方法:DFS递归。

  • 思路:直径 = 任意两个结点路径长度中的最大值,可以看做树内的某一节点的左子树节点数l + 右子树节点数r - 1,所有节点的l + r - 1中的最大值即为直径。定义一个递归函数计算经过的左右子树的节点数l + r,函数返回给定节点为根的子树的深度。递归搜索每个节点并设一个全局变量 ans 记录 l + r 的最大值,最后返回 ans 即为树的直径。

  • 代码:

    class Solution {
        int ans = 0;
        public int diameterOfBinaryTree(TreeNode root) {
            depth(root);
            return ans;
        }
        //返回最大的 L+R
        public int depth(TreeNode root){
            if(root == null) return 0;
            int l = depth(root.left);
            int r = depth(root.right);
            ans = Math.max(ans, l + r);//找到最大直径
            return Math.max(l, r) + 1;//树的深度
        }
    }
    
226,翻转二叉树,easy

翻转一棵二叉树。

示例:

输入:

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

输出:

     4
   /   \
  7     2
 / \   / \
9   6 3   1
  • 方法一:递归。

  • 思路:分别对左右子树都进行翻转,再交换。

  • 代码:

    class Solution {
        public TreeNode invertTree(TreeNode root) {
            //递归
            if(root == null) return null;
            TreeNode leftTree = root.left;//保存原来的左子树
            root.left = invertTree(root.right);
            root.right = invertTree(leftTree);
            return root;
        }
    }
    
  • 方法二:借助栈(DFS)。

  • 思路:先将根节点压入栈。栈非空时,弹出栈顶节点,如果弹出节点的左右子节点有非空,将其压入栈,并进行交换,重复此过程直到栈空。

  • 代码:

    class Solution {
        public TreeNode invertTree(TreeNode root) {
            if(root == null) return null;
            Stack<TreeNode> stack = new Stack<>();
            stack.push(root);
            while(!stack.isEmpty()){
                TreeNode node = stack.pop();
                if(node.left != null){
                    stack.push(node.left);
                }
                if(node.right != null){
                    stack.push(node.right);
                }
                TreeNode temp = node.right;
                node.right = node.left;
                node.left = temp;
            }
            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

注意: 合并必须从两个树的根节点开始。

  • 方法一:递归。(dfs)

  • 思路:新建一棵树,如果原来两棵树的节点都存在,直接相加;如果有一个不存在,返回另一个节点。递归节点的左右子节点,作为新树的左右子树。

  • 代码:

    class Solution {
        public TreeNode mergeTrees(TreeNode t1, TreeNode t2) {
            if (t1 == null && t2 == null)
                return null;
            if(t1 == null || t2 == null){
                return t1 == null ? t2 : t1;
            }
            TreeNode newTree = new TreeNode(t1.val + t2.val);
            newTree.left = mergeTrees(t1.left,t2.left);
            newTree.right = mergeTrees(t1.right,t2.right);
            return newTree;
        }
    }
    
112,路径总和,easy

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

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

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

          5
         / \
        4   8
       /   / \
      11  13  4
     /  \      \
    7    2      1

返回 true, 因为存在目标和为 22 的根节点到叶子- 方法一:递归(dfs)

  • 思路:以当前根节点为例,如果为null,返回false;如果为叶子节点,判断当前节点值是否与sum相等并返回;如果不是叶子节点,递归搜索左右子节点。

  • 代码:

    class Solution {
        public boolean hasPathSum(TreeNode root, int sum) {
            if(root == null) return false;
            if(root.left == null && root.right == null) return root.val == sum;
            return hasPathSum(root.left,sum - root.val) || hasPathSum(root.right, sum - root.val);
        }
    }
    
113,路径总和Ⅱ,midium

给定一个二叉树和一个目标和,找到所有从根节点到叶子节点路径总和等于给定目标和的路径。

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

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

          5
         / \
        4   8
       /   / \
      11  13  4
     /  \    / \
    7    2  5   1

返回:

[
   [5,4,11,2],
   [5,8,4,5]
]
  • 方法:递归回溯(dfs)。

  • 思路:

    • 回溯条件:
      1. 节点为空— 如果当前节点为空,说明节点没有孩子,循着这条路径,已经找不到符合条件的路径。
      2. 节点为叶子节点— 如果当前节点是叶子节点并且它的值满足题目要求,则它所在的路径就是满足要求的。
  • 代码:

    class Solution {
        List<List<Integer>> res = new ArrayList<>();
        public List<List<Integer>> pathSum(TreeNode root, int sum) {
            dfs(root, sum, res, new ArrayList<>());
            return res;
        }
        public void dfs(TreeNode root, int sum, List<List<Integer>> res, List<Integer> list){
            if(root == null) return;
            //把当前节点值加入到list中
             list.add(root.val);
            //叶子节点且此节点值=sum
            if(root.left == null && root.right == null && root.val == sum) res.add(new ArrayList(list));
           
            //还没到叶子节点,继续从左右节点向下找
            dfs(root.left, sum - root.val, res, list);
            dfs(root.right, sum - root.val, res, list);
            //防止分支污染,遍历完当前节点的左子树、右子树,说明经过这个节点的路径已经被遍历完,因此要回溯到当前节点的父节点
            list.remove(list.size() - 1);
        }
    }
    

    关于递归与回溯的详解

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 

给定的树 t:

   4
  / \
 1   2

返回 false。

  • 方法:建立一个递归函数,判断两棵树是否相等。

  • 代码:

    class Solution {
        public boolean isSubtree(TreeNode s, TreeNode t) {
            //第二棵树为空,一定是子树
            if(t == null) return true;
            //第一棵树为空,没有子树
            if(s == null) return false;
            //递归比较t是否是s的左子树和右子树的一部分,或s与t是两棵相同的树
            return isSubtree(s.left, t) || isSubtree(s.right, t) || isSameTree(s, t);
        }
        //判断两棵树是否相同
        public boolean isSameTree(TreeNode s, TreeNode t){
            if(s == null && t == null) return true;
            //如果其中有一个节点为空或两节点值不相等时,返回false
            if(s == null || t == null || s.val != t.val) return false;
            //递归比较左子树和右子树是否相同
            return isSameTree(s.left, t.left) && isSameTree(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
  • 方法一:递归。

  • 思路:建立一个递归函数,移动两个指针遍历这棵树,判断根节点的左右子树是否对称。在主函数调用此递归函数,参数为根的左子节点和右子节点。

  • 代码:

    class Solution {
        public boolean isSymmetric(TreeNode root) {
            if(root == null) return true;
            return check(root.left, root.right);
        }
        //用两个指针检查树是否对称
        public boolean check(TreeNode p, TreeNode q){
            if(p == null && q == null) return true;
            if(p == null || q == null || p.val != q.val) return false;
            return check(p.left, q.right) && check(p.right, q.left);
        }
    }
    
111,二叉树的最小深度,easy

给定一个二叉树,找出其最小深度。

最小深度是从根节点到最近叶子节点的最短路径上的节点数量。

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

img

示例 1:

输入:root = [3,9,20,null,null,15,7]
输出:2

示例 2:

输入:root = [2,null,3,null,4,null,5,null,6]
输出:5
  • 方法一:递归。

    • 结束条件:当root为空,返回0
    • 递归体:若左右子树皆空,返回1;若左子树和右子树有非空的,记录其最小路径,最后返回结果为最小路径 ➕ 1
  • 代码:

    class Solution {
        public int minDepth(TreeNode root) {
            if(root == null) return 0;
            //1.当root的左右子树都为空
            if(root.left == null && root.right == null) return 1;
    
            int mindep = Integer.MAX_VALUE;
            //2.root的左子树或右子树有不为空,计算其最小路径
            if(root.left != null){
                mindep = Math.min(minDepth(root.left), mindep);
            }
            
            if(root.right != null){
                mindep = Math.min(minDepth(root.right), mindep);
            }
            //返回最小路径+1
            return mindep + 1;
        }
    }
    
  • 错误原因:想法是dfs 来递归左右子树找它们的最小路径,然后取最小。但这样可能在 [2,null,3,null,4,null,5,null,6] 结构中不成立,因为root的左子树为空,所以要加一个判断。

404,左叶子之和,easy

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

示例:

    3
   / \
  9  20
    /  \
   15   7

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

  • 方法一:递归dfs

  • 思路:所有左叶子节点之和,需要遍历整棵树,采用dfs。

    • 递归出口:节点为空或为叶子节点

    • 递归条件:

      • 如果左子树不为空,判断左子节点是否为叶子节点:若不是,递归调用左子节点

        • 如果右子树为空,结果不变;若不为空,上面的结果+ 递归调用右子节点
      • 否则左子树为空,右子树一定不为空,只需判断右子节点是不是叶子节点,若不是,递归调用右子节点

      • 最后返回结果

  • 代码:

    class Solution {
        public int sumOfLeftLeaves(TreeNode root) {
    
            if(root == null || isLeaf(root)) return 0;
    
            return dfs(root);
        }
        public int dfs(TreeNode root){
            if(root == null || isLeaf(root)) return 0;
            int res = Integer.MAX_VALUE;
            if(root.left != null){
                res = isLeaf(root.left) ? root.left.val : dfs(root.left);
                if(root.right != null)
                res += isLeaf(root.right) ? 0 : dfs(root.right);
            }
            else{
                res = isLeaf(root.right) ? 0 : dfs(root.right);
            }
            return res;
        }
        public boolean isLeaf(TreeNode node){
            return node.left == null && node.right == null; 
        }
    }
    

    更简洁的版本:

    public int sumOfLeftLeaves(TreeNode root) {
        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) {
            if(root == null) return 0;
            if(root.left == null && root.right == null) return 0;
            int res = Integer.MAX_VALUE;
            if(root.left != null && root.right == null){
               res = sumOfLeftLeaves(root.left) + root.left.val;
            }
            if(root.left == null && root.right != null){
                res = sumOfLeftLeaves(root.right);
            }
            if(root.left != null && root.right != null){
                res = sumOfLeftLeaves(root.left) + root.left.val + sumOfLeftLeaves(root.right);
            }
            return res;
        }
    }
    
687,最长同值路径,medium

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

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

示例 1:

输入:

          5
         / \
        4   5
       / \   \
      1   1   5

输出:

2

示例 2:

输入:

          1
         / \
        4   5
       / \   \
      4   4   5

输出:

2
  • 方法一:递归。(dfs)

  • 思路:

在这里插入图片描述

  • 最长路径分为2种情况:

    1. 以root为起点,经过左子树或右子树,如(2)
    2. 不以root为起点,root为中间点,如(1)
  • 辅助函数helper,计算以每一个节点为起点的最长同值路径maxLength,在过程中可以得到以root为根节点的树的最长同值路径ans。

  • 代码:

    class Solution {
        int ans;
        //以root为根节点的树的最长同值路径
        public int longestUnivaluePath(TreeNode root) {
            if(root == null) return 0;
            helper(root);
            return ans;
        }
        //以root为起点的最长同值路径
        public int helper(TreeNode root){
            if(root == null) return 0;
            int maxLength = 0;
            //以左、右子节点为起点的最长同值路径
            int leftLength = helper(root.left);
            int rightLength = helper(root.right);
            //如果左、右子树都非空 且 root.val == root.left.val == root.right.val,更新ans
            if(root.left != null && root.right != null && root.val == root.left.val  && root.val == root.right.val){
                ans = Math.max(ans, leftLength + rightLength + 2);
            }
            int leftPath = 0;
            int rightPath = 0;
            //如果左右子树有非空且子节点的值与root的值相等,以根节点为起点的最长同值路径为leftPath,rightPath中的最大值
            if(root.left != null && root.left.val == root.val){
                leftPath  = leftLength + 1;
            }
            if(root.right != null && root.right.val == root.val){
                rightPath = rightLength + 1;
            }
            //取左右子树的最长同值路径的最大值
            maxLength = Math.max(leftPath,rightPath);
            //更新ans
            ans = Math.max(ans, maxLength);
            return maxLength;
        }
    }
    

    简洁版:

    private int path = 0;
    
    public int longestUnivaluePath(TreeNode root) {
        dfs(root);
        return path;
    }
    
    private int dfs(TreeNode root){
        if (root == null) return 0;
        int left = dfs(root.left);
        int right = dfs(root.right);
        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 = Math.max(path, leftPath + rightPath);
        return Math.max(leftPath, rightPath);
    }
    
671,二叉树中第二小的节点,easy

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

更正式地说,root.val = min(root.left.val, root.right.val) 总成立。

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

示例 1:

img

输入:root = [2,2,5,null,null,5,7]
输出:5
解释:最小的值是 2 ,第二小的值是 5 。

示例 2:

img

输入:root = [2,2,2]
输出:-1
解释:最小的值是 2, 但是不存在第二小的值。
  • 题意解析:每个树的根节点的值都为这棵树所有节点最小的值,所有节点中第二小的值即只比根节点大的值。

  • 方法一:递归。

    • 递归出口:节点为空,返回 -1。
    • 递归体:
      • 左右子节点都为空,返回 -1。
      • 根节点取的是左子节点的值,递归左子节点得到只比这个值大的值(或 -1,即此节点为叶子节点)
      • 根节点取的是右子节点的值,递归右子节点得到只比这个值大的值(或 -1,即此节点为叶子节点)
    • 结果:
      • left 、right 如果都不为 -1,取最小值并返回
      • left 不为 -1(right 为 -1),返回left
      • 否则(left为 -1),返回right(-1 或 root.right.val或递归结果)
  • 代码:

    class Solution {
        public int findSecondMinimumValue(TreeNode root) {
            if(root == null) return -1;
            if(root.left == null && root.right == null) return -1;
            int left = root.left.val;
            //如果左子节点是最小值,递归左子节点,得到以左子节点为根的树的第二小的值 或 -1
            if(left == root.val){
                left = findSecondMinimumValue(root.left);//-1 或 以root.left为根的树的第二小的值
            }
            int right = root.right.val;
            //如果右子节点是最小值,递归右子节点,得到以右子节点为根的树的第二小的值 或 -1
            if(right == root.val){
                right = findSecondMinimumValue(root.right);//-1 或 以root.right为根的树的第二小的值
            }
            //如果两边的树递归结果都不为-1(都正常),返回它们的最小值,即在root为根的树中只比root.val大
            if(left != -1 && right != -1) return Math.min(left,right);
            //如果左子树正常,返回左子树
            if(left != -1) return left; 
            //否则返回右子树递归值(-1 或 以root.right为根的树的第二小的值)
            else return right;
            
        }
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值