算法通关村第8关——二叉树的深度和高度问题(白银)

算法通关村第8关——二叉树的深度和高度问题(白银)

    • 1. 最大深度问题
    • 2. 平衡二叉树
    • 3.最小深度
    • 4.N叉树的最大深度

1. 最大深度问题

leetcode 104. 二叉树的最大深度

这道题代码很简单,但是思考方式跟以前不一样,不过还是同一个划分的方法,将树分为左右子树来调用

	3	    3    3 	     3
   / \	   /  	  \	    / \
  9	 20	  9	       20	

对于node(3),最大深度自然是左右子结点+1,左右子节点有的可能为空,只要有一个,

树的最大高度就是1+1=2。然后再增加几个结点:

算法通关村第8关——二叉树的深度和高度问题(白银)_第1张图片

很显然相对于node(20),最大深度自然是左右子结点+1,左右子节点有的可能为空,只要有一个,树的最大高度就是1+1=2,用代码表示就是:int depth = 1 + max(leftDepth, rightDepth);

而对于3,则是左右子树深度最大的那个然后再+1,具体谁更大,则不必关心。所以对于node(3)的判断逻辑就是:

int	leftDepth = getDepth(node.left); // 左
int	rightDepth = getDepth(node.right); // 右
int	depth = 1 + max(leftDepth, rightDepth); // 中

那什么时候结束呢,这里仍然是root == null返回0就行了。至于入参,自然是要处理的子树的根节点root,而返回值则是当前root所在子树的最大高度。所以合在一起就是:

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

2. 平衡二叉树

leetcode 110. 平衡二叉树

这道题跟上一题的差别就是多一个判断,如何判断return回去的高度做比较,如果为0,OK,为-1,高度不对

class Solution {
    public boolean isBalanced(TreeNode root) {
        return height(root) >= 0;
    }
    
    public int height(TreeNode root) {
        if (root == null) {
            return 0;
        }
        
        int leftHeight = height(root.left);
        int rightHeight = height(root.right);
        
        if (leftHeight == -1 || rightHeight == -1 || Math.abs(leftHeight - rightHeight) > 1) {
            return -1;
        } else {
            return Math.max(leftHeight, rightHeight) + 1;
        }
    }
}

判断左右子树的高度差是否大于1,如果大于1则返回-1表示不平衡,否则返回左右子树中较大的高度加1。

最终,如果根节点的高度为-1,则整个二叉树不平衡;否则,整个二叉树平衡。

优化:

不过这段代码看起来并不好理解,可以把if拿出来,不过这段代码稍微好理解一点,但是没有上面那一段好

class Solution {
    public boolean isBalanced(TreeNode root) {
        return height(root) != -1;
    }

    public int height(TreeNode root) {
        if (root == null) {
            return 0;
        }
        int leftHeight = height(root.left);
        if (leftHeight == -1) {
            return -1;
        }
        int rightHeight = height(root.right);
        if (rightHeight == -1) {
            return -1;
        }
        if (Math.abs(leftHeight - rightHeight) > 1) {
            return -1;
        }
        return Math.max(leftHeight, rightHeight) + 1;
    }
}

然后我使用gpt帮忙优化一下,但是时间会比较长,应该是数据达到一定的量的时候,会更好,下面直接贴出代码,主要是使用一个缓存Map来保存已经计算过的节点高度,避免重复计算

class Solution {
    private Map<TreeNode, Integer> heightCache = new HashMap<>();

    public boolean isBalanced(TreeNode root) {
        return height(root) >= 0;
    }
    
    public int height(TreeNode root) {
        if (root == null) {
            return 0;
        }

        // 检查缓存中是否已经计算过该节点的高度
        if (heightCache.containsKey(root)) {
            return heightCache.get(root);
        }
        
        int leftHeight = height(root.left);
        int rightHeight = height(root.right);
        
        // 提前终止判断
        if (leftHeight == -1 || rightHeight == -1 || Math.abs(leftHeight - rightHeight) > 1) {
            return -1;
        } else {
            int maxHeight = Math.max(leftHeight, rightHeight) + 1;
            // 将计算结果加入缓存
            heightCache.put(root, maxHeight);
            return maxHeight;
        }
    }
}

不过在leetcode执行会发现最开始那个更快,因为第二段代码可能由于缓存操作而导致运行时间稍长,因为缓存操作需要额外的内存和时间开销。只有在数据量比较大时,重复计算的次数才会明显增加,进而体现出第二段代码的优势。

所以在一棵二叉树中节点数量大约在10^4级别时,第二段代码相对第一段代码的性能优势可能开始显现。这个是一个大概的估计,实际效果还与具体的二叉树结构以及其他因素有关。

3.最小深度

leetcode 111. 二叉树的最小深度

这里的关键问题是题目中说的:最小深度是从根节点到最近叶子节点的最短路径上的节点数量,也就是最小深度的一层必须要有叶子结点,因此不能直接用。

这里的核心问题仍然是分析终止条件:

● 如果左子树为空,右子树不为空,说明最小深度是 1 + 右子树的深度。

● 反之,右子树为空,左子树不为空,最小深度是 1 + 左子树的深度。

最后如果左右子树都不为空,返回左右子树深度最小值 + 1 。

class Solution {
    public int minDepth(TreeNode root) {
        if(root == null){
            return 0;
        }
        if(root.left == null && root.right == null){
            return 1;
        }
        int min_depth = Integer.MAX_VALUE;
        if(root.left != null){
            min_depth = Math.min(minDepth(root.left), min_depth);
        }
        if(root.right != null){
            min_depth = Math.min(minDepth(root.right), min_depth);
        }
        return min_depth + 1;
    }
}

4.N叉树的最大深度

leetcode 559. N 叉树的最大深度

这个题的实现和上一个题的最大区别就是处理子树时加了个处理List的for循环

class Solution {
    public int maxDepth(Node root) {
        if(root == null){
            return 0;
        }
        if(root.children.isEmpty()){
            return 1;
        }
        List<Integer> heigths = new LinkedList<>();
        for(Node item : root.children){
            heigths.add(maxDepth(item));
        }
        return Collections.max(heigths)+1;
    }
}

这样就OK了,不过可以进一步优化代码,也是使用一个辅助函数,在递归过程中将已经计算过的节点深度保存起来

class Solution {
    private Map<Node, Integer> depthCache = new HashMap<>(); // 创建一个缓存,用于保存已经计算过的节点深度

    public int maxDepth(Node root) {
        if (root == null) {
            return 0; // 如果根节点为空,说明是空树,深度为0
        }

        if (depthCache.containsKey(root)) {
            return depthCache.get(root); // 如果缓存中已经存在当前节点的深度,则直接返回缓存中的结果
        }

        int maxChildDepth = 0; // 初始化最大子节点深度为0
        for (Node child : root.children) { // 遍历所有子节点
            int childDepth = maxDepth(child); // 递归计算每个子节点的深度
            maxChildDepth = Math.max(maxChildDepth, childDepth); // 取所有子节点深度的最大值
        }

        int depth = maxChildDepth + 1; // 当前节点的深度等于最大子节点的深度加1
        depthCache.put(root, depth); // 将当前节点的深度加入缓存

        return depth; // 返回当前节点的深度
    }
}

这段代码的实现思路如下:

  • 首先判断根节点是否为空,如果为空则说明是空树,深度为0。
  • 然后检查缓存中是否已经存在当前节点的深度。如果存在,则直接返回缓存中的结果,避免重复计算。
  • 如果缓存中不存在当前节点的深度,则遍历所有子节点,递归计算每个子节点的深度,并取得最大的子节点深度。
  • 当前节点的深度等于最大子节点深度加1,并将结果保存到缓存中。
  • 最后返回当前节点的深度。

你可能感兴趣的:(算法,数据结构,算法,笔记,java,数据结构)