【面试题】二叉树的深度(三种题目变体解析,DFS/BFS两种方法,Java/Python双语言实现)

题目描述

在这里插入图片描述

解法一:层序遍历(BFS)

按层遍历树的所有节点,每遍历完一层,最大深度+1,直到最后一个叶子节点。这其实就是广度优先搜索的策略,BFS一般借助队列实现。

Java

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public int maxDepth(TreeNode root) {
        if(root == null) return 0;
        int max_depth = 0; 
        Queue<TreeNode> q = new LinkedList<> ();
        q.offer(root);

        while(q.size()!=0) {
            int current_level_size = q.size(); // 当前层的节点个数
            for(int i=0; i<current_level_size; i++) { // 弹出当前层次的所有节点,并将每个节点的孩子节点入队
                TreeNode node = q.poll();
                if(node.left != null) q.offer(node.left);
                if(node.right != null) q.offer(node.right);
            }
            max_depth++; // 每遍历完一层的节点,max_depth+1
        }

        return max_depth;
    }
}

Python

# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution(object):
    def maxDepth(self, root):
        """
        :type root: TreeNode
        :rtype: int
        """
        if not root: return 0 # 树为空,返回0
        max_depth = 0 # 最大深度初始化为0
        queue = []
        queue.append(root)

        while queue:
            current_level_size = len(queue) # 当前层次的节点个数
            for _ in range(current_level_size): # 弹出当前层次的所有节点,并将每个节点的孩子节点入队
                node = queue.pop(0)
                if node.left: queue.append(node.left)
                if node.right: queue.append(node.right)
            max_depth += 1 # 每遍历完一层的节点,max_depth+1

        return max_depth

解法二:后序遍历(DFS)

显然,此树的深度等于左子树的深度与右子树的深度中的最大值 +1。因此,我们需要分别先计算出左子树和右子树的深度,取其中较大者返回再+1,这其实可以通过后序遍历(深度优先搜索)的思想实现,DFS一般通过递归或借助栈实现。

Java

递归实现,代码非常简洁易懂

class Solution {
    public int maxDepth(TreeNode root) {
        if(root == null) return 0;
        return 1+Math.max(maxDepth(root.left), maxDepth(root.right));
    }
}

辅助栈实现

class Solution {
    public int maxDepth(TreeNode root) {
        if(root == null) return 0;
        LinkedList<Pair<TreeNode, Integer>> stack = new LinkedList<> (); // 栈中存放的是键值对<节点,节点所在深度>
        stack.push(new Pair<>(root, 1)); // 栈中初始只包含根节点,深度为1

        int max_depth = 0;
        while(!stack.isEmpty()) {
            Pair<TreeNode, Integer> pair = stack.pop();
            TreeNode node = pair.getKey();
            int current_depth = pair.getValue();
            if(node.left == null && node.right == null) // 若当前节点为叶子节点,更新最大深度
                max_depth = Math.max(current_depth, max_depth);
            if(node.left != null) // 若当前节点存在左孩子,则压入栈,深度+1
                stack.push(new Pair<>(node.left, current_depth+1));
            if(node.right != null) // 若当前节点存在右孩子,则压入栈,深度+1
                stack.push(new Pair<>(node.right, current_depth+1));
        }
        return max_depth;
    }
}

Python

递归实现

class Solution(object):
    def maxDepth(self, root):
        """
        :type root: TreeNode
        :rtype: int
        """
        if not root: return 0
        # 分治法,递归分别计算左子树和右子树的最大深度,并取其较大者,+1是指要算上当前节点所在的层
        return 1+max(self.maxDepth(root.left), self.maxDepth(root.right))

借助栈实现

class Solution(object):
    def maxDepth(self, root):
        """
        :type root: TreeNode
        :rtype: int
        """
        if not root: return 0
        stack = [(1, root),] # 栈中初始只包含根节点,当前深度为1
        max_depth = float('-inf')

        while stack:
            depth, node = stack.pop()
            if not node.left and not node.right: # 遇到当前节点为叶子节点时,更新最大深度
                max_depth = max(depth, max_depth) # 只保留其中最大的深度
            if node.left: # 若存在左孩子,则压入栈,深度+1
                stack.append((depth + 1, node.left))
            if node.right: # 若存在右孩子,则压入栈,深度+1
                stack.append((depth + 1, node.right))
        
        return max_depth

题目变体1

此题实际上要求的是求一颗二叉树的最大深度,如果是要求二叉树的最小深度可以参考这篇博客

题目变体2

在这里插入图片描述

解法一:先序遍历(自顶向下)

构造一个获取当前子树的深度的函数 depth(root) ,通过比较某子树的左右子树的深度差 abs(depth(root.left) - depth(root.right)) <= 1 是否成立,来判断某子树是否是二叉平衡树。若所有子树都平衡,则此树平衡。

Java

class Solution {
    public boolean isBalanced(TreeNode root) {
        if(root == null) return true;
        int diff = Math.abs(maxDepth(root.left)- maxDepth(root.right)); // 计算根结点左右子树的深度差
        if(diff > 1) return false; // 如果根结点左右子树的深度相差超过 1,则不是平衡二叉树
        else return isBalanced(root.left) && isBalanced(root.right); // 否则,递归判断其左右子树是否也为平衡二叉树
    }
    // 计算二叉树的最大深度的函数
    public int maxDepth(TreeNode root) {
        if(root == null) return 0;
        return 1 + Math.max(maxDepth(root.left), maxDepth(root.right));
    }
}

但是,这种方法会产生大量重复计算(主要体现在计算当前子树的深度和后边递归计算其左右子树的深度时,左右子树中的每个结点会被重复遍历多次),时间复杂度较高。

解法二:后序遍历(自底向上)

如果我们⽤后序遍历的⽅式遍历⼆叉树的每⼀个结点, 在遍历到⼀个结点之前我们就已经遍历了它的左右⼦树。 只要在遍历每个结点的时候记录它的深度(某⼀结点的深度等于它到叶节点的路径的长度) , 我们就可以⼀边遍历⼀边判断每个结点是不是平衡的。若遇到某子树不是平衡二叉树时直接“剪枝”,该树一定不是平衡二叉树,不用继续向上判断了。

递归函数:

  1. 若当前子树的左右子树深度相差不超过 1(<=1),则返回当前子树的深度,即其左右子树深度的较大者 + 1
  2. 若当前子树的左右子树深度相差超过 1(>2),则该子树不是平衡二叉树,返回 -1;

主函数:
若 recur(root) != -1 ,则说明此树平衡,返回 true ; 否则返回 false。

class Solution {
    public boolean isBalanced(TreeNode root) {
        if(root == null) return true;
        return recur(root) != -1;
    }

    /*
    用后序遍历的方式遍历二叉树的每个节点(从底至顶),先左子树,再右子树,最后根节点
    考虑到需要同时记录各个节点的深度和其是否符合平衡性要求,这里的返回值设为int,用一个特殊值-1来表示出现不平衡的节点的情况,而不是一般采用的boolean
    */
    public int recur(TreeNode root) {
        if(root == null) return 0; // 递归终止条件:越过叶子节点,返回深度 0
        int left = recur(root.left);
        if(left == -1) return -1; // 递归终止条件:当前子树的左子树返回深度为-1,代表左子树不是平衡二叉树
        int right = recur(root.right);
        if(right == -1) return -1; // 递归终止条件:当前子树的右子树返回深度为-1,代表右子树不是平衡二叉树

        // 最开始计算的是左子树最左侧的一个叶节点,其左右子节点不存在,left=0,right=0,满足条件,返回该叶节点的深度max(0,0)+1=1;
        return Math.abs(left-right) <= 1 ? Math.max(left, right)+1 : -1;
    }
}

参考

https://leetcode-cn.com/problems/ping-heng-er-cha-shu-lcof/solution/mian-shi-ti-55-ii-ping-heng-er-cha-shu-cong-di-zhi/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值