二叉树问题求解

本文介绍了如何利用树形动态规划(DP)和递归策略解决二叉树相关问题,如判断平衡性、验证二叉搜索树以及寻找最近公共祖先。通过三个具体的例子详细阐述了思路和模板,每个例子中都包含后序遍历的思想,并展示了如何利用子节点信息维护父节点状态。

在求解一般树形dp的问题可以考虑使用递归求解 -》 后序遍历思想

树形dp问题:从子结点向上维护出父结点状态

模板:

public class Main {

    public class Info {
        // 用于维护的结点信息
    }
 
    public Info process(Node root, ...) {
        if (root == null) {
            return new Info();
        }

        Info leftInfo = process(root.left);
        Info rightInfo = process(root.right);
        
        // 利用子结点维护当前结点状态
        
        return new Info();
    }
}

例1 判断平衡性

 思路:每次以当前结点x考虑

a. 不考虑x

    1. 左子树平衡

    2. 右子树平衡

b. 考虑x -》 维护状态

    1. 左子树,右子树都平衡,且高度差不大于1    

class Solution {

    public class Info {
        boolean isBalance;  // 是否平衡
        int height;  // 树高度,用于之后判断子树高度差
        public Info() {

        }
        public Info(boolean isBalance, int height) {
            this.isBalance = isBalance;
            this.height = height;
        }
    }

    public boolean isBalanced(TreeNode root) {
        Info res = isBalancedHelp(root);
        return res.isBalance;
    }

    public Info isBalancedHelp(TreeNode root) {
        if (root == null) {  // 边界
            return new Info(true, 0);
        }
        Info leftInfo = isBalancedHelp(root.left);  // 左子树信息
        Info rightInfo = isBalancedHelp(root.right);  // 右子树信息

        // 维护当前结点状态
        int height = Math.max(leftInfo.height, rightInfo.height) + 1;

        boolean isBalance = true;
        if (!leftInfo.isBalance) isBalance = false;
        if (!rightInfo.isBalance) isBalance = false;
        if (Math.abs(leftInfo.height - rightInfo.height) > 1) isBalance = false;

        return new Info(isBalance, height);
    }

}

 例2 验证二叉搜索树

 思路:

a. 不考虑x

    1. 左子树是二叉搜索树?

    2. 右子树是二叉搜索树?

b. 考虑x

    1. 当前结点x.val, 左子树最大值 < x.val? 右子树最小值 > x.val ?

维护Info属性:isBalanced,是否平衡; max/min:结点最大值和最小值

class Solution {

    public class Info {
        boolean isBST;
        int max;
        int min;
        public Info() {}
        public Info(boolean isBST, int max, int min) {
            this.isBST = isBST;
            this.max =  max;
            this.min = min;
        }
    }

    public boolean isValidBST(TreeNode root) {
        return process(root).isBST;
    }
    public Info process(TreeNode root) {
        if (root == null) {
            return null;
        }
        Info leftInfo = process(root.left);
        Info rightInfo = process(root.right);

        int max = root.val;
        int min = root.val;
        if (leftInfo != null) {
            max = Math.max(max, leftInfo.max);
            min = Math.min(min, leftInfo.min);
        } 
        if (rightInfo != null) {
            max = Math.max(max, rightInfo.max);
            min = Math.min(min, rightInfo.min);
        }

        boolean isBST = true;
        if (leftInfo != null && !leftInfo.isBST) isBST = false;
        if (rightInfo != null && !rightInfo.isBST) isBST = false;
        if (leftInfo != null && leftInfo.max >= root.val) isBST = false;
        if (rightInfo != null && rightInfo.min <= root.val) isBST = false;
 

        return new Info(isBST, max, min);
    }
}

例3: 二叉树的最近公共祖先

 思路:

方案一:用集合Map,Set统计p所有可能的祖先结点,然后用q进行遍历

public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        Map<TreeNode, TreeNode> map = new HashMap<>();
        findParent(map, root);
        Set<TreeNode> set = new HashSet<>();
        while (!set.contains(p)) {
            set.add(p);
            p = map.get(p);
        }
        while (!set.contains(q)) {
            q = map.get(q);
        }
        return q;
    }
    public void findParent(Map<TreeNode, TreeNode> map, TreeNode root) {
        if (root.left != null) {
            map.put(root.left, root);
            findParent(map, root.left);
        }
        if (root.right != null) {
            map.put(root.right, root);
            findParent(map, root.right);
        }
    }
}

思路2:递归求解 -》 后序遍历

不考虑x

        1. 左子树中找到祖先

        2. 右子树中找到祖先

        3. 左右都没有

考虑x

        1.有p, q,祖先就是x

        2.x == p, 有q,祖先是x

        3.x == q,有p, 祖先是x

class Solution {
  
    public class Info {
        boolean findP;
        boolean findQ;
        TreeNode ans;
        public Info(boolean findP, boolean findQ, TreeNode ans) {
            this.findQ = findQ;
            this.findP = findP;
            this.ans = ans;
        }
    }
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        return process(root, p, q).ans;
    }

    public Info process(TreeNode root, TreeNode p, TreeNode q) {
        if (root == null) {
            return new Info(false, false, null);
        }
        Info leftInfo = process(root.left, p, q);
        Info rightInfo = process(root.right, p, q);

        boolean findP = (leftInfo.findP || root == p || rightInfo.findP);
        boolean findQ = (leftInfo.findQ || root == q || rightInfo.findQ);

        TreeNode ans = null;
        if (leftInfo.ans != null) {  // 前两种说的任一子树已经找到答案,直接赋值即可
            ans = leftInfo.ans;
        } else if (rightInfo.ans != null) {
            ans = rightInfo.ans;
        } else {  
            if (findQ && findP) {  // 说明p,q分布于左右子树,那么公共祖先就是root
                ans = root;
            }
        }
        return new Info(findP, findQ, ans);
    }
}

<think>好的,我现在需要帮助用户理解如何通过编程实现二叉树的各种操作,包括节点计数、叶子节点计数、查找节点层次以及不同的遍历方法。用户特别提到了先序、中序、后序和层次遍历的递归实现。首先,我需要回顾二叉树的基本概念和相关算法,确保自己对这些内容的理解正确。 首先,节点计数。二叉树节点个数的计算应该是遍历所有节点并统计总数。无论是哪种遍历方式,只要访问每个节点并计数,都可以得到总数。递归实现的话,可以分解为根节点加上左子树和右子树的节点数。 接下来是叶子节点计数。叶子节点的定义是没有子节点的节点,即左右子树都为空。同样可以通过遍历所有节点,判断是否为叶子节点,然后累加数量。递归方法的话,每个节点如果是叶子节点就返回1,否则返回左右子树叶节点数的和。 然后是查找特定节点的层次。这里需要确定某个值所在的层次,可能需要从根节点开始逐层查找,记录当前层次,当找到目标值时返回该层次。递归的话,可以传递当前层次,遍历左右子树时层次加1,直到找到节点或返回未找到。 关于遍历算法,用户提到了先序、中序、后序和层次遍历。其中层次遍历通常使用队列实现,而不是递归,但用户要求递归实现,这可能需要一些技巧。不过常规的层次遍历递归实现不太直接,可能需要结合深度和队列,或者使用辅助函数记录当前层。不过通常层次遍历用迭代更自然,但用户可能需要递归的方法,这里需要思考如何实现。 接下来需要确保每个部分的代码正确,并且用中文解释清楚。同时,用户要求使用递归实现层次遍历,这可能是个挑战,因为传统的层次遍历是用队列迭代实现的。可能需要通过前序遍历的方式,记录每个节点的层次,然后按照层次收集节点,但这可能不属于严格意义上的层次遍历,但可以满足用户需求。或者,可能需要使用递归函数来模拟层次遍历,逐层处理节点,但这种方法可能复杂度较高。 现在需要组织回答的结构,分步骤解释每个操作的实现方法,并给出对应的代码示例。同时,注意用户提到的先序、中序、后序和层次遍历的递归实现,所以需要确保这些遍历的递归代码正确,并解释清楚。 在引用方面,用户提供的参考引用是关于先序遍历的,但需要确保引用标识正确放置在相关内容之后,比如在讲解先序遍历时引用[^1]。此外,要生成相关问题,可能需要围绕二叉树的操作、遍历的应用、不同遍历方式的区别、递归与迭代实现的比较等方面提出问题。 需要检查每个代码块是否正确,特别是叶子节点计数的判断条件和递归终止条件。例如,在叶子节点计数中,递归函数应该处理当前节点是否是叶子节点,如果是则返回1,否则递归处理左右子树并相加。 对于节点层次的查找,可能需要一个辅助函数来传递当前层次,并在递归调用时增加层次。如果节点值存在重复,可能需要处理多个节点的情况,但通常假设节点值唯一,找到第一个出现的层次即可。 最后,确保所有数学公式和代码块格式正确,使用$...$或$$...$$,并且回答使用中文,结构清晰,步骤明确。同时生成的相关问题要涵盖用户可能感兴趣的扩展内容,比如时间复杂度、应用场景、不同遍历方式的比较等。</think>### 二叉树基础操作实现 #### 一、节点个数统计 通过递归遍历所有节点,统计总数: $$节点数 = 1 + 左子树节点数 + 右子树节点数$$ ```python def count_nodes(root): if not root: return 0 return 1 + count_nodes(root.left) + count_nodes(root.right) ``` #### 二、叶子节点统计 叶子节点需满足无子节点: $$叶子数 = \begin{cases} 1, & \text{左右子节点均为空} \\ 左子树叶子数 + 右子树叶子数, & \text{其他情况} \end{cases}$$ ```python def count_leaves(root): if not root: return 0 if not root.left and not root.right: return 1 return count_leaves(root.left) + count_leaves(root.right) ``` #### 三、节点层次查找 从根节点(第1层)递归搜索: ```python def find_level(root, target, level=1): if not root: return -1 if root.val == target: return level left = find_level(root.left, target, level+1) if left != -1: return left return find_level(root.right, target, level+1) ``` #### 四、遍历算法实现 1. **先序遍历**(根-左-右)[^1] ```python def pre_order(root, result): if root: result.append(root.val) pre_order(root.left, result) pre_order(root.right, result) ``` 2. **中序遍历**(左-根-右) ```python def in_order(root, result): if root: in_order(root.left, result) result.append(root.val) in_order(root.right, result) ``` 3. **后序遍历**(左-右-根) ```python def post_order(root, result): if root: post_order(root.left, result) post_order(root.right, result) result.append(root.val) ``` 4. **层次遍历**(递归辅助实现) ```python def level_order(root): def _helper(node, level, res): if not node: return if len(res) == level: res.append([]) res[level].append(node.val) _helper(node.left, level+1, res) _helper(node.right, level+1, res) result = [] _helper(root, 0, result) return result ``` ### 关键点总结 1. 递归终止条件需明确(`if not root`) 2. 层次遍历通过记录层级参数实现伪广度优先 3. 叶子节点判定需同时检查左右子节点 4. 时间复杂度均为$O(n)$,空间复杂度为$O(h)$(h为树高)
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值