1 题目
给你一个二叉树的根节点 root ,树中每个节点都存放有一个 0 到 9 之间的数字。
每条从根节点到叶节点的路径都代表一个数字:
- 例如,从根节点到叶节点的路径
1 -> 2 -> 3表示数字123。
计算从根节点到叶节点生成的 所有数字之和 。
叶节点 是指没有子节点的节点。
示例 1:

输入:root = [1,2,3]
输出:25
解释:
从根到叶子节点路径 1->2 代表数字 12
从根到叶子节点路径 1->3 代表数字 13
因此,数字总和 = 12 + 13 = 25
示例 2:

输入:root = [4,9,0,5,1]
输出:1026
解释:
从根到叶子节点路径 4->9->5 代表数字 495
从根到叶子节点路径 4->9->1 代表数字 491
从根到叶子节点路径 4->0 代表数字 40
因此,数字总和 = 495 + 491 + 40 = 1026
提示:
- 树中节点的数目在范围
[1, 1000]内 0 <= Node.val <= 9- 树的深度不超过
10
2 代码实现
c++
cpp
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
private:
int dfs (TreeNode* node ,int curSum){
if (node == nullptr){
return 0 ;
}
curSum = curSum * 10 + node -> val;
if (node -> left == nullptr && node -> right == nullptr){
return curSum ;
}
return dfs(node -> left ,curSum) + dfs(node -> right , curSum);
}
public:
int sumNumbers(TreeNode* root) {
return dfs (root, 0);
}
};
js
javascript
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number}
*/
var sumNumbers = function(root) {
const dfs = function (node, curSum){
if (node == null){
return 0 ;
}
curSum = curSum * 10 + node.val ;
if (node.left == null && node.right == null){
return curSum ;
}
return dfs(node.left , curSum) + dfs(node.right , curSum);
}
return dfs(root , 0 );
};
思考
怎么将根节点到叶子节点的遍历数字记录下来,如何记录树的深度。可以先存成字符串在转换吗?还是要怎么做,不知道怎么实现?
题解
解题思路
解决这个问题的核心是深度优先搜索(DFS):
- 从根节点开始,递归遍历每个节点;
- 每访问一个节点,就将当前累积的数字
*10 + 当前节点值(比如路径 1->2,就是 1*10+2=12); - 当遍历到叶节点(左右子节点都为空)时,将当前累积的数字加入总和;
- 递归遍历左子树和右子树,最终得到所有路径的数字之和。
这种方式不需要额外记录树的深度,也不需要字符串转换,直接通过数值运算完成,效率更高。
C++ 完整代码实现
cpp
#include <iostream>
using namespace std;
// 定义二叉树节点结构
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode() : val(0), left(nullptr), right(nullptr) {}
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
};
class Solution {
public:
int sumNumbers(TreeNode* root) {
// 调用递归函数,初始累积值为0
return dfs(root, 0);
}
private:
// 深度优先搜索递归函数:node为当前节点,currentSum为到当前节点的累积数字
int dfs(TreeNode* node, int currentSum) {
// 边界条件:节点为空,返回0(无贡献)
if (node == nullptr) {
return 0;
}
// 计算到当前节点的累积数字:前序值*10 + 当前节点值
currentSum = currentSum * 10 + node->val;
// 叶节点:左右子节点都为空,返回当前累积的数字(这条路径的最终值)
if (node->left == nullptr && node->right == nullptr) {
return currentSum;
}
// 非叶节点:递归遍历左子树和右子树,求和
return dfs(node->left, currentSum) + dfs(node->right, currentSum);
}
};
// 测试用例
int main() {
// 示例1:root = [1,2,3]
TreeNode* root1 = new TreeNode(1);
root1->left = new TreeNode(2);
root1->right = new TreeNode(3);
Solution s;
cout << "示例1输出:" << s.sumNumbers(root1) << endl; // 预期输出25
// 示例2:root = [4,9,0,5,1]
TreeNode* root2 = new TreeNode(4);
root2->left = new TreeNode(9);
root2->right = new TreeNode(0);
root2->left->left = new TreeNode(5);
root2->left->right = new TreeNode(1);
cout << "示例2输出:" << s.sumNumbers(root2) << endl; // 预期输出1026
// 释放内存(避免内存泄漏)
delete root1->left;
delete root1->right;
delete root1;
delete root2->left->left;
delete root2->left->right;
delete root2->left;
delete root2->right;
delete root2;
return 0;
}
代码关键部分解释
- 二叉树节点定义:符合题目中树的结构,包含值、左子节点、右子节点;
- sumNumbers 函数 :对外接口,调用递归函数
dfs,初始累积值为 0; - dfs 递归函数 :
- 边界处理:节点为空时返回 0,避免空指针访问;
- 累积计算:
currentSum * 10 + node->val是核心,比如路径 1->2,先算 010+1=1,再算 110+2=12; - 叶节点判断:左右子节点都为空时,当前累积值就是这条路径的数字,直接返回;
- 递归遍历:非叶节点时,递归处理左右子树,将结果相加(所有路径的和);
- 测试用例:还原题目中的两个示例,验证代码正确性。
补充:你提到的 "字符串存储" 方案(备选)
如果想先用字符串存储路径再转换,也可以实现(效率稍低,但逻辑更直观),核心代码如下:
cpp
class Solution {
public:
int sum = 0; // 存储总和
int sumNumbers(TreeNode* root) {
string path = "";
dfs_str(root, path);
return sum;
}
void dfs_str(TreeNode* node, string path) {
if (node == nullptr) return;
path += to_string(node->val); // 拼接当前节点值到路径字符串
// 叶节点:转换字符串为数字,加入总和
if (node->left == nullptr && node->right == nullptr) {
sum += stoi(path);
return;
}
// 递归遍历左右子树
dfs_str(node->left, path);
dfs_str(node->right, path);
}
};
总结
- 核心解法是深度优先搜索(DFS),遍历所有根到叶的路径;
- 推荐用数值累加 (
currentSum * 10 + node->val)的方式,无需字符串转换,效率更高; - 递归终止条件:节点为空返回 0,叶节点返回当前累积值,非叶节点返回左右子树结果之和。
这个实现满足题目所有约束(节点数≤1000、深度≤10),时间复杂度 O (n)(每个节点遍历一次),空间复杂度 O (h)(h 为树的深度,递归栈空间)。
3 题目
给你一棵完全二叉树 的根节点 root ,求出该树的节点个数。
完全二叉树 的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层(从第 0 层开始),则该层包含 1~ 2h 个节点。
示例 1:

输入:root = [1,2,3,4,5,6]
输出:6
示例 2:
输入:root = []
输出:0
示例 3:
输入:root = [1]
输出:1
提示:
- 树中节点的数目范围是
[0, 5 * 104] 0 <= Node.val <= 5 * 104- 题目数据保证输入的树是 完全二叉树
进阶: 遍历树来统计节点是一种时间复杂度为 O(n) 的简单解决方案。你可以设计一个更快的算法吗?
4 代码实现
c++
cpp
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
int countNodes(TreeNode* root) {
if (root == nullptr){
return 0 ;
}
int left = countNodes(root -> left );
int right = countNodes(root -> right );
return left + right + 1 ;
}
};
cpp
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
int countNodes(TreeNode* root) {
if (root == nullptr){
return 0 ;
}
int leftHeight = 0 ;
TreeNode* leftNode = root -> left ;
while (leftNode != nullptr){
leftHeight ++ ;
leftNode = leftNode -> left ;
}
int rightHeight = 0 ;
TreeNode* rightNode = root -> right ;
while(rightNode != nullptr){
rightHeight ++ ;
rightNode = rightNode ->right ;
}
if (leftHeight == rightHeight ){
return (1 << (leftHeight + 1) ) - 1 ;
}
return 1 + countNodes(root -> left ) + countNodes(root -> right);
}
};
js
javascript
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number}
*/
var countNodes = function(root) {
if (root == null){
return 0 ;
}
let left = countNodes(root.left);
let right = countNodes(root.right);
return 1 + left + right ;
};
javascript
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number}
*/
var countNodes = function(root) {
if (root == null){
return 0 ;
}
let leftNode = root.left;
let leftHeight = 0 ;
while (leftNode != null){
leftHeight++;
leftNode = leftNode.left ;
}
let rightNode = root.right ;
let rightHeight = 0 ;
while (rightNode != null){
rightHeight ++ ;
rightNode = rightNode.right ;
}
if (leftHeight == rightHeight ){
return ( 1 << (leftHeight + 1)) - 1;
}
return 1 + countNodes(root.left) + countNodes(root.right);
};
思考
这个问题同样也是深度优先搜索,不知道怎么递归,完全二叉树只有最底下一层不是满的。终止条件是,如果一个节点的左右孩子节点不是全满,那么说明已经找到了有缺失的叶子节点的最后一层。
也还是不知道怎么写。
题解
看了思路但还是写不出代码,核心问题是对递归的 "终止条件""递归逻辑" 和 "完全二叉树特性的落地" 没有拆解得足够细。
从最基础的暴力 DFS 开始,再到进阶优化版,每个代码行都解释清楚,确保你能跟着写出来。
先明确核心问题:你写不出来的根源
- 不知道递归该 "返回什么"(终止条件模糊);
- 不知道递归该 "算什么"(核心逻辑没落地);
- 完全二叉树的特性不知道怎么转化为代码(比如 "怎么判断满二叉树""怎么算高度")。
第一步:先搞定「暴力 DFS 版」(O (n))------ 先保证能写出来
这个版本不利用完全二叉树特性,就是通用的 "统计二叉树节点数" 方法,逻辑最简单,是所有递归的基础,先把这个写会。
核心思路(暴力版)
递归的本质是 "分解问题":
- 一个树的节点数 = 1(当前节点) + 左子树的节点数 + 右子树的节点数;
- 终止条件:如果节点是空的(
nullptr),说明没有节点,返回 0。
C++ 完整代码(暴力版)
cpp
#include <iostream>
using namespace std;
// 二叉树节点定义(题目给定,不用改)
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode() : val(0), left(nullptr), right(nullptr) {}
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
};
class Solution {
public:
int countNodes(TreeNode* root) {
// 步骤1:递归终止条件 ------ 空节点,节点数为0
if (root == nullptr) {
return 0;
}
// 步骤2:核心逻辑 ------ 当前节点(1) + 左子树节点数 + 右子树节点数
int leftCount = countNodes(root->left); // 递归算左子树
int rightCount = countNodes(root->right); // 递归算右子树
return 1 + leftCount + rightCount; // 汇总
}
};
// 测试代码(直接复制就能运行)
int main() {
// 示例1:root = [1,2,3,4,5,6]
TreeNode* root = new TreeNode(1);
root->left = new TreeNode(2);
root->right = new TreeNode(3);
root->left->left = new TreeNode(4);
root->left->right = new TreeNode(5);
root->right->left = new TreeNode(6);
Solution s;
cout << "节点总数:" << s.countNodes(root) << endl; // 输出6
// 释放内存(可选,避免内存泄漏)
delete root->left->left;
delete root->left->right;
delete root->left;
delete root->right->left;
delete root->right;
delete root;
return 0;
}
逐行解释(暴力版)------ 确保你能看懂每一步
- 节点定义 :
TreeNode是题目给定的结构,记住就行,不用自己想; - 终止条件
if (root == nullptr):- 比如递归到叶子节点的左孩子(空),这时候返回 0,说明这个位置没有节点;
- 这是递归的 "出口",没有这个会无限递归,必须写;
leftCount = countNodes(root->left):- 把 "统计左子树节点数" 的问题,交给递归函数自己解决;
- 你不用管递归到底怎么算的,只需要知道:调用这个函数,就能得到左子树的节点数;
return 1 + leftCount + rightCount:1是当前节点自己;leftCount是左子树所有节点;rightCount是右子树所有节点;- 三者相加就是当前树的总节点数。
暴力版怎么练?------ 手把手教你写
- 先写函数头:
int countNodes(TreeNode* root); - 先写终止条件:
if (root == nullptr) return 0;; - 再写递归计算左右子树:
int left = countNodes(root->left); int right = countNodes(root->right);; - 最后返回总和:
return 1 + left + right;。
这个版本你一定要先写出来,哪怕抄一遍,也能建立 "递归能写出来" 的信心。
第二步:进阶版(利用完全二叉树特性,O (log²n))------ 理解 "为什么快"
题目要求 "更快的算法",核心是利用完全二叉树的特性:
完全二叉树 = 除了最后一层,其他层都是满的;最后一层的节点都靠左。
先记住 2 个关键公式 / 结论
- 满二叉树节点数公式 :如果一棵满二叉树的高度是
h(高度定义:从根到叶子的层数,根是第 1 层),节点数 =2^h - 1;- 比如高度为 3 的满二叉树:
2^3 -1 = 7个节点; - 高度为 2 的满二叉树:
2^2 -1 = 3个节点;
- 比如高度为 3 的满二叉树:
- 完全二叉树的高度计算 :
- 左高度:从当前节点一直往左走,能走几步,就是左高度(因为完全二叉树靠左);
- 右高度:从当前节点一直往右走,能走几步,就是右高度;
- 判断满二叉树 :如果一个节点的左高度 == 右高度 → 这个树是满二叉树,直接用公式算节点数;
- 如果左高度 > 右高度 → 右子树是满二叉树,左子树需要递归算。
C++ 完整代码(进阶版)
cpp
#include <iostream>
using namespace std;
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode() : val(0), left(nullptr), right(nullptr) {}
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
};
class Solution {
public:
int countNodes(TreeNode* root) {
// 步骤1:终止条件 ------ 空节点返回0(和暴力版一样)
if (root == nullptr) {
return 0;
}
// 步骤2:计算左高度(一直往左走)
int leftHeight = 0;
TreeNode* leftNode = root->left;
while (leftNode != nullptr) {
leftHeight++;
leftNode = leftNode->left; // 完全二叉树,往左走到底就是高度
}
// 步骤3:计算右高度(一直往右走)
int rightHeight = 0;
TreeNode* rightNode = root->right;
while (rightNode != nullptr) {
rightHeight++;
rightNode = rightNode->right; // 往右走到底
}
// 步骤4:判断是否是满二叉树
if (leftHeight == rightHeight) {
// 满二叉树节点数公式:2^(leftHeight+1) - 1(因为leftHeight是从0开始算的)
// 用位运算:1 << (leftHeight+1) 等价于 2^(leftHeight+1),更快
return (1 << (leftHeight + 1)) - 1;
}
// 步骤5:不是满二叉树,递归算左右子树(和暴力版一样,但次数少很多)
return 1 + countNodes(root->left) + countNodes(root->right);
}
};
// 测试代码(和暴力版一样)
int main() {
TreeNode* root = new TreeNode(1);
root->left = new TreeNode(2);
root->right = new TreeNode(3);
root->left->left = new TreeNode(4);
root->left->right = new TreeNode(5);
root->right->left = new TreeNode(6);
Solution s;
cout << "节点总数:" << s.countNodes(root) << endl; // 输出6
// 释放内存
delete root->left->left;
delete root->left->right;
delete root->left;
delete root->right->left;
delete root->right;
delete root;
return 0;
}
进阶版逐行解释(重点拆 "特性怎么落地")
- 计算左 / 右高度 :
- 比如示例 1 的根节点:
- 左高度:root->left(2)→ root->left->left(4)→ 再往左是 null,所以 leftHeight=2;
- 右高度:root->right(3)→ 再往右是 null,所以 rightHeight=1;
- 因为 leftHeight != rightHeight,所以不是满二叉树,需要递归;
- 如果是满二叉树(比如 root=[1,2,3]),leftHeight=1,rightHeight=1,直接返回
(1<<2)-1=3;
- 比如示例 1 的根节点:
- 位运算
1 << (leftHeight+1):1 << n表示把 1 左移 n 位,等价于2^n,比如1<<3=8(2^3);- 为什么是
leftHeight+1?因为 leftHeight 是从 0 开始算的(比如走了 2 步,高度是 3 层);
- 为什么更快 :
- 暴力版要遍历所有 n 个节点;
- 进阶版每次递归都能排除一半的节点(因为有一侧是满二叉树,直接公式算),时间复杂度从 O (n) 降到 O (log²n)。
进阶版怎么写?------ 按步骤来
-
先写终止条件:
if (root == nullptr) return 0;; -
写循环算左高度:
cppint leftH = 0; TreeNode* l = root->left; while (l) { leftH++; l = l->left; } -
写循环算右高度:
cppint rightH = 0; TreeNode* r = root->right; while (r) { rightH++; r = r->right; } -
写满二叉树判断:
if (leftH == rightH) return (1 << (leftH+1)) -1;; -
最后递归:
return 1 + countNodes(root->left) + countNodes(root->right);。
为什么你之前写不出来?------ 针对性解决
- 递归终止条件没先写 :
- 写递归的第一步,先想 "什么时候停",而不是先想 "怎么算";
- 比如这道题,先写
if (root == nullptr) return 0;,这是所有递归的基础;
- 把问题拆太小 :
- 不要一上来就想 "整个树怎么算",而是想 "当前节点该算什么";
- 当前节点只需要做 3 件事:① 判断是不是空;② 算左右子树;③ 加自己;
- 完全二叉树特性没转化为代码 :
- 不要只记 "最后一层不满",而是记 "怎么判断满二叉树"(左右高度相等)、"怎么算高度"(一直往左 / 右走);
- 先写暴力版,再在暴力版基础上加 "高度计算 + 满二叉树判断",一步步改,不是一步到位。
总结(核心要点,记下来)
- 暴力版(必掌握) :
- 终止条件:空节点返回 0;
- 递归逻辑:
1 + 左子树节点数 + 右子树节点数;
- 进阶版(优化) :
- 左高度 = 一直往左走的步数,右高度 = 一直往右走的步数;
- 左高度 == 右高度 → 满二叉树,用公式
(1 << (h+1)) -1; - 否则递归算左右子树;
- 写代码的顺序:先写终止条件 → 再写核心逻辑(暴力版) → 再加优化(进阶版)。
你现在先把暴力版代码抄一遍,运行成功,再试着自己写一遍;然后在暴力版基础上,加高度计算的代码,改成进阶版。每一步都确保能运行,不要跳步,这样就能慢慢找到感觉了。如果哪一步写不出来,比如 "循环算高度",随时问,我再拆细。
基础版
cpp
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
int countNodes(TreeNode* root) {
if (root == nullptr){
return 0 ;
}
int left = countNodes(root -> left );
int right = countNodes(root -> right );
return left + right + 1 ;
}
};
进阶版
自己乱写又是错的
cpp
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
int countNodes(TreeNode* root) {
if (root == nullptr){
return 0 ;
}
int leftHeight = 0 ;
while (root -> left != nullptr){
leftHeight ++ ;
root = root -> left ;
}
int rightHeight = 0 ;
while (root -> right != nullptr){
rightHeight ++ ;
root = root ->right ;
}
if (leftHeight == rightHeight ){
return (1 << (leftHeight + 1) - 1 ) ;
}
return 1 + countNodes(root -> left ) + countNodes(root -> right);
}
};
你写的代码思路方向是对的,但有两个致命错误导致逻辑完全走偏,我先指出问题,再给你修正后的完整代码,最后逐行解释错误原因,确保你能彻底理解。
核心错误分析
错误 1:修改了原始的 root 指针(最关键)
你在计算 leftHeight 时,直接把 root = root->left,这会让 root 指针指向左子树的最深处(比如示例 1 的根节点最后会指向 4),后续计算 rightHeight 和递归时,用的已经不是原来的根节点了,而是被修改后的节点,逻辑直接错乱。
错误 2:位运算优先级错误
(1 << (leftHeight + 1) - 1 ) 中,减法 -1 的优先级高于位运算 <<,导致实际计算的是 1 << (leftHeight + 1 - 1) = 1 << leftHeight,而不是正确的 (1 << (leftHeight + 1)) - 1,公式完全错了。
修正后的完整代码
cpp
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
int countNodes(TreeNode* root) {
// 终止条件:空节点返回0
if (root == nullptr) {
return 0;
}
// 错误1修复:用临时变量计算高度,不修改原始root
int leftHeight = 0;
TreeNode* leftNode = root->left; // 临时变量,遍历左子树
while (leftNode != nullptr) {
leftHeight++;
leftNode = leftNode->left; // 移动临时变量,不影响root
}
int rightHeight = 0;
TreeNode* rightNode = root->right; // 临时变量,遍历右子树
while (rightNode != nullptr) {
rightHeight++;
rightNode = rightNode->right; // 移动临时变量,不影响root
}
// 错误2修复:给位运算加括号,保证优先级
if (leftHeight == rightHeight) {
return (1 << (leftHeight + 1)) - 1;
}
// 递归计算左右子树,此时root还是原始根节点,正确
return 1 + countNodes(root->left) + countNodes(root->right);
}
};
逐行解释错误修复细节
错误 1 修复:临时变量代替原始 root
cpp
// 你的错误写法(修改了root)
while (root -> left != nullptr){
leftHeight ++ ;
root = root -> left ; // root被改成左子节点,后续全错
}
// 正确写法(用临时变量)
TreeNode* leftNode = root->left; // 临时变量指向root的左子树
while (leftNode != nullptr) {
leftHeight++;
leftNode = leftNode->left; // 只移动临时变量,root不变
}
- 比如示例 1 的根节点是 1,计算左高度时:
- 你的写法:root 最终会变成 4(左子树最深处),后续计算右高度时,是用 4 的右节点(null),rightHeight=0,然后递归时 root->left 是 null,结果完全错;
- 正确写法:root 始终是 1,leftNode 遍历完左子树后销毁,不影响原始 root。
错误 2 修复:位运算优先级
cpp
// 你的错误写法(优先级错)
return (1 << (leftHeight + 1) - 1 ) ;
// 实际计算:1 << (leftHeight + 1 - 1) = 1 << leftHeight
// 正确写法(加括号保证先算位运算,再减1)
return (1 << (leftHeight + 1)) - 1;
// 比如leftHeight=2,先算1<<3=8,再减1=7(正确的满二叉树节点数)
- C++ 运算符优先级:
-(减法)><<(位左移),所以必须给1 << (leftHeight + 1)加括号,否则计算顺序错误。
测试验证(以示例 1 为例)
示例 1:root = [1,2,3,4,5,6]
- 计算左高度:root->left=2 → leftNode=2→4,leftHeight=2;
- 计算右高度:root->right=3 → rightNode=3→null,rightHeight=1;
- leftHeight≠rightHeight,递归计算:
- 1 + countNodes(2) + countNodes(3);
- countNodes (2):左高度 = 1(2→4),右高度 = 0(2→5→null),递归得 1+countNodes (4)+countNodes (5)=1+1+1=3;
- countNodes (3):左高度 = 0(3→6),右高度 = 0(3→null),满二叉树,返回 (1<<1)-1=1;
- 总和:1+3+2=6(正确)。
错误:
- 核心错误 1:不要修改递归的原始入参(root),计算高度时必须用临时变量遍历;
- 核心错误 2:位运算和算术运算混合时,注意优先级,加括号保证计算顺序;
- 递归关键:递归过程中,原始根节点必须保持不变,否则后续递归的入参全错。
终于通过了
cpp
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
int countNodes(TreeNode* root) {
if (root == nullptr){
return 0 ;
}
int leftHeight = 0 ;
TreeNode* leftNode = root -> left ;
while (leftNode != nullptr){
leftHeight ++ ;
leftNode = leftNode -> left ;
}
int rightHeight = 0 ;
TreeNode* rightNode = root -> right ;
while(rightNode != nullptr){
rightHeight ++ ;
rightNode = rightNode ->right ;
}
if (leftHeight == rightHeight ){
return (1 << (leftHeight + 1) ) - 1 ;
}
return 1 + countNodes(root -> left ) + countNodes(root -> right);
}
};
5 小结
见多识广,知道这个解法就算是暴力也应该做出来。多积累,多反思。补充一下位运算,还有dfs的实现,其实大可以自己private一个函数function再左,要是看题目里面只给一个参数确实不好写。思路都有,怎么就是写不来代码,不知道怎么去提升。其实也许拿来背诵也该记住,写多了就有记忆了?
召唤ai老师...
一、核心知识点总结(两道题通用 + 专属)
1. 深度优先搜索(DFS)核心逻辑(通用)
DFS 解决二叉树问题的本质是分解问题 + 明确终止条件,所有递归写法都遵循这个模板:
cpp
// 通用DFS模板(二叉树)
返回值 dfs(当前节点, 状态参数) {
// 1. 终止条件:空节点/叶子节点(根据题目定)
if (节点为空) return 基准值;
// 2. 处理当前节点:更新状态(比如累积数字/统计高度)
状态更新逻辑;
// 3. 递归处理左右子树:合并结果
return 左子树递归结果 + 右子树递归结果;
}
- 129 题:状态参数是
curSum(累积数字),终止条件是 "空节点返回 0,叶子节点返回 curSum"; - 222 题:暴力版无额外状态,终止条件是 "空节点返回 0";进阶版状态是 "左右高度",终止条件新增 "满二叉树直接返回公式结果"。
2. 129. 求根到叶数字之和(专属要点)
- 核心技巧:数值累加
curSum * 10 + node->val,避免字符串转换(高效); - 关键:递归时
curSum是 "值传递",每层递归的curSum独立,不会互相干扰; - 备选思路:字符串拼接(直观但效率低),适合理解路径遍历逻辑。
3. 222. 完全二叉树节点数(专属要点)
| 版本 | 时间复杂度 | 核心逻辑 | 关键注意点 |
|---|---|---|---|
| 暴力 DFS | O(n) | 1 + 左子树节点数 + 右子树节点数 | 终止条件先写(空节点返回 0) |
| 进阶优化 | O(log²n) | 利用满二叉树特性减少递归 | ① 用临时变量算高度,不修改 root;② 位运算加括号保证优先级 |
- 满二叉树公式:
(1 << (高度+1)) - 1(1 << n等价于2^n,位运算更快); - 高度计算:左高度 = 一直往左走的步数,右高度 = 一直往右走的步数,相等则为满二叉树。
二、高频踩坑点(你亲身遇到的)
- 修改原始入参 :计算高度时直接改
root,导致后续递归入参错误(必须用临时变量leftNode/rightNode); - 运算符优先级 :位运算
<<优先级低于减法-,必须给1 << (h+1)加括号; - 终止条件缺失 / 写错:递归先想 "什么时候停",再想 "怎么算",空节点返回 0 是二叉树递归的基础;
- 思路到代码的断层:知道 "用高度判断满二叉树",但不知道 "用临时变量遍历算高度",本质是 "把抽象思路拆成具体代码步骤" 的能力不足。
三、提升代码落地能力的方法(针对性解决 "思路有但写不出")
- 先 "抄" 后 "改",再 "写" :
- 第一步:抄通能运行的代码(比如暴力版),逐行注释含义,理解每一步的作用;
- 第二步:改代码(比如给暴力版加高度计算),只改一小部分,运行验证是否正确;
- 第三步:脱离参考,从头写,卡壳时只看 "核心思路",不看完整代码。
- 模板化记忆,而非死记代码 :
- 记住 DFS 的 "终止条件→处理当前节点→递归左右子树" 三步模板,不管题目怎么变,套模板填逻辑;
- 比如遇到新的二叉树题,先写
if (root == nullptr) return 0;,再想当前节点该处理什么。
- 刻意练 "拆步骤" :
- 拿到思路(比如 "算高度判断满二叉树"),先拆成代码小步骤:① 定义临时变量
leftNode = root->left;② 循环遍历算 leftHeight;③ 同理算 rightHeight;④ 判断相等则用公式; - 把抽象思路拆成 "能写出来的代码行",是新手到熟手的关键。
- 拿到思路(比如 "算高度判断满二叉树"),先拆成代码小步骤:① 定义临时变量
- 积累 "小技巧" :
- 二叉树递归中,原始入参(如
root)尽量不修改,用临时变量操作; - 位运算替代幂运算(
1 << n代替pow(2, n)),既快又避免浮点误差; - 私有函数(
private)拆分逻辑(比如 129 题把 DFS 写成私有函数),适配题目给定的函数参数。
- 二叉树递归中,原始入参(如
四、最终小结
- 二叉树 DFS 的核心是 "终止条件 + 分解子问题",先保证暴力版能写出来,再谈优化;
- 思路到代码的落地,关键是把 "抽象思路拆成具体代码步骤",比如 "算高度" 拆成 "定义临时变量→循环遍历→计数";
- 踩坑不可怕,每解决一个坑(比如改 root、运算符优先级),就是一次实打实的进步,记下来这些坑,下次遇到直接规避。
你现在已经能从 "写不出" 到 "踩坑后能改对",这是最关键的突破!接下来可以找同类二叉树 DFS 题(比如路径总和、二叉树的所有路径),用同样的方法练,写多了模板会刻在脑子里,思路到代码的转化会越来越快。