二叉树高频面试题精讲:求深度/节点数/完全二叉树判断的5种递归套路

二叉树高频面试题精讲:求深度/节点数/完全二叉树判断的5种递归套路

面试官让你手写一个求二叉树深度的函数,你自信满满地写下递归代码,结果他追问:“如果这棵树有十万个节点,你的递归会栈溢出吗?” 或者,当被问到如何判断一棵树是否是完全二叉树时,你是否能立刻想到不止一种解法,并能清晰地说出各自的优劣?在技术面试中,二叉树相关的问题几乎是必考题,但很多候选人的回答往往停留在“会写”的层面,缺乏对问题本质、性能边界和代码健壮性的深入思考。今天,我们不谈那些泛泛而谈的遍历,而是聚焦于深度、节点数和完全性判断这三个核心高频考点,拆解出五种极具实战价值的递归“套路”。这些套路不仅是解题模板,更是理解分治思想和递归本质的钥匙,能帮助你在面试中展现出超越普通候选人的思维深度。

1. 递归的基石:理解“分治”与“状态带回”

在深入具体问题之前,我们必须重新审视递归。很多教程把递归讲得神乎其神,但面试中,面试官真正想考察的是你将大问题分解为子问题的能力,也就是分治思想。对于二叉树,这个思想天然契合:任何一棵二叉树,都可以看作“根节点 + 左子树 + 右子树”。处理整棵树的问题,就转化为处理左右两棵子树的问题,再合并结果。

然而,一个常见的误区是只关注递归的“递”而忽略了“归”。递归函数在“归”的过程中,如何将子问题的答案有效地“带回来”,是写出优雅代码的关键。这通常有两种方式:

  1. 利用返回值:这是最直观的方式。函数直接返回子问题的计算结果。
  2. 通过参数传递状态:有时我们需要在递归过程中累积一些信息(比如路径、全局状态),这时可以通过引用或指针参数来传递。

让我们看一个简单的例子来感受这两种方式的区别:计算二叉树节点总数。

套路一:纯返回值分治

int countNodes(TreeNode* root) {
    if (root == nullptr) {
        return 0; // 基本情况:空树节点数为0
    }
    // 分治:总数 = 左子树节点数 + 右子树节点数 + 1(当前根节点)
    int leftCount = countNodes(root->left);
    int rightCount = countNodes(root->right);
    return leftCount + rightCount + 1;
}

这种写法清晰体现了分治:先解决左子树和右子树两个子问题,再将它们的解与当前节点合并。它的空间复杂度是O(h),其中h是树高,因为递归调用栈的深度最多为树高。

一个关键的优化点:注意上面代码中,我们显式地用leftCountrightCount保存了递归结果。有些同学会写成return countNodes(root->left) + countNodes(root->right) + 1;,这在逻辑上没错,但可读性稍差,且不利于调试时观察中间结果。在面试中,采用第一种更清晰的写法会显得你代码风格更严谨。

那么,有没有其他“套路”呢?我们来看一个看似巧妙但实则危险的变体。

套路二(不推荐):利用静态变量或全局变量

int total = 0; // 全局变量
void countNodesGlobal(TreeNode* root) {
    if (root == nullptr) return;
    total++;
    countNodesGlobal(root->left);
    countNodesGlobal(root->right);
}
// 调用前需要重置 total = 0

这种方法违反了函数的“无副作用”原则,且在多线程或多次调用时会带来灾难。在面试中,如果你提出这种方法,一定要紧接着指出它的致命缺陷,并说明为什么返回值的方式更优。这反而能展示你的全面思考。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值