
🔥承渊政道: 个人主页
❄️个人专栏: 《C语言基础语法知识》 《数据结构与算法》 《C++知识内容》 《Linux系统知识》 《算法刷题指南》 《测评文章活动推广》 《大模型语言路线学习》
✨逆境不吐心中苦,顺境不忘来时路!✨ 🎬 博主简介:

在算法学习的进阶阶段,递归、搜索与回溯几乎是绕不开的核心主题,而其中二叉树深搜模型更是连接"树结构理解"和"搜索思想应用"的关键桥梁.很多同学在刷题时会发现:明明知道二叉树要用 DFS,明明也写过递归,但一到具体题目就容易出现思路混乱、边界不清、状态设计困难的问题.归根结底,不是题目太难,而是对"深搜模型"的底层逻辑还没有真正拆透.事实上,二叉树上的很多经典题目,无论是遍历、路径统计、树形 DP、最近公共祖先,还是回溯式搜索,本质上都可以归结为几个稳定且高频的 DFS 思维框架.只要抓住这些框架,建立起"从节点出发如何思考、从子树返回什么信息、递归过程如何组织状态"的统一认知,原本零散的题型就会逐渐串联起来,形成完整的方法体系.因此,本文将围绕"二叉树深搜模型拆解"这一主线,系统梳理递归、搜索与回溯在树结构中的应用方式,从基础思想入手,逐步分析常见模型与易错点,并结合经典题型进行全面突破.希望你读完之后,不只是会做几道题,而是真正掌握二叉树DFS背后的通用套路,建立起面对同类问题时能够快速识别模型、精准下手的能力.废话不多说,下面跟着小编的节奏🎵一起去疯狂的学习吧!
目录
- 1.二叉树的深度优先搜索算法思想背景
- 2.计算布尔二叉树的值(OJ题)
- 3.求根节点到叶节点数字之和(OJ题)
- 4.二叉树剪枝(OJ题)
- 5.验证二叉搜索树(OJ题)
- 6.二叉搜索树中的第k小的元素(OJ题)
- 7.二叉树的所有路径(OJ题)
1.二叉树的深度优先搜索算法思想背景
二叉树的深度优先搜索,本质思想是:沿着一条分支尽可能往下走,走到不能再走时,再回退到上一个结点,改走另一条分支.这是一种很自然的"探索式"思维方式,背景可以从几个角度理解.
1.思想来源
它来源于人们处理"树状结构"问题的直觉.
把二叉树想成一棵真正的树,从根结点出发,你有两条路可选:左子树和右子树.深度优先搜索不是一层一层看,而是先选一条路一直走到底,再退回来找别的路.这和人在迷宫里"先一直往前探,走不通再回头"的策略很像.
2.为什么适合二叉树
二叉树本身就是一种递归定义的结构:
(1)一棵二叉树由根结点、左子树、右子树组成
(2)左子树和右子树本身又都是二叉树
所以处理整棵树的问题时,可以自然拆成:
(1)先处理当前结点
(2)再处理左子树
(3)再处理右子树
这正好对应深度优先搜索的递归思想.
3.核心算法思想
DFS 的核心是两点:
(1)深入:优先沿某个孩子结点一直向下
(2)回溯:当当前路径走完后,返回父结点继续搜索其他分支
在二叉树中,常见 DFS 访问方式有三种:
(1)前序遍历:根 → 左 → 右
(2)中序遍历:左 → 根 → 右
(3)后序遍历:左 → 右 → 根
它们本质上都是 DFS,只是"访问根结点"的时机不同.
4.数学与程序设计背景
DFS 特别适合递归,是因为它符合"分治"思想:
(1)大问题:遍历整棵树
(2)小问题:遍历左子树、遍历右子树
(3)终止条件:遇到空结点就返回
所以代码通常非常简洁,例如前序遍历思想可以写成:
cpp
def dfs(root):
if root is None:
return
print(root.val) # 访问当前结点
dfs(root.left) # 深入左子树
dfs(root.right) # 深入右子树
5.这种思想的意义
DFS 在二叉树中很重要,因为它能解决很多问题,比如:
(1)遍历所有结点
(2)求树的高度/深度
(3)判断两棵树是否相同
(4)查找某条路径
(5)求最近公共祖先
(6)判断是否平衡树
因为这些问题都适合通过"先解决子树,再组合结果"的方式完成.
6.一句话概括
二叉树深度优先搜索的思想背景就是:利用二叉树天然的递归结构,按照"一条路走到底,再回退分支"的方式遍历和求解问题.
2.计算布尔二叉树的值(OJ题)

算法思路:解法(递归):
本题可以被解释为:
1.对于规模为n的问题,需要求得当前节点值.
2.节点值不为0或1时,规模为n的问题可以被拆分为规模为n-1的子问题:
(1)所有子节点的值;
(2)通过子节点的值运算出当前节点值.
3.当问题的规模变为n=1时,即叶子节点的值为0或1,我们可以直接获取当前节点值为0或1.
算法流程:
递归函数设计:bool evaluateTree(TreeNode* root)
- 返回值:当前节点值;
- 参数:当前节点指针.
- 函数作用:求得当前节点通过逻辑运算符得出的值.
递归函数流程:
- 判断是否为叶子节点
因为这棵树是完全二叉树,只要左孩子为空,就说明是叶子节点,直接返回对应布尔值. - 递归拆解问题
把整棵树的计算,拆成左子树计算+右子树计算,递归求解两个子问题. - 合并子问题结果
根据当前节点的运算符:
2 = OR(或):只要左/右有一个为 true,结果就是true
3 = AND(与):必须左/右都为true,结果才是true - 返回最终结果
自底向上完成整棵树的计算.

核心代码
cpp
class Solution
{
public:
//核心递归函数:输入根节点,返回整棵树的布尔计算结果
bool evaluateTree(TreeNode* root)
{
//1.递归终止条件(叶子节点)
//布尔二叉树是【完全二叉树】:非叶子节点一定有左右孩子
//因此 左孩子=空 ⇨ 当前节点是叶子节点
if (root->left == nullptr)
//叶子节点:0 → false,1 → true(三元运算符简写)
return root->val == 0 ? false : true;
//2.递归计算左右子树
//递归计算【左子树】的结果
bool left = evaluateTree(root->left);
//递归计算【右子树】的结果
bool right = evaluateTree(root->right);
//3.根据当前节点运算符计算结果
//节点值=2 → 逻辑或 OR (left | right)
//节点值=3 → 逻辑与 AND (left & right)
return root->val == 2 ? left | right : left & right;
}
};
完整测试代码
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:
bool evaluateTree(TreeNode* root)
{
if (root->left == nullptr)
return root->val == 0 ? false : true;
bool left = evaluateTree(root->left);
bool right = evaluateTree(root->right);
return root->val == 2 ? left | right : left & right;
}
};
int main()
{
Solution s;
// 测试1:
// 2
// / \
// 1 3
// / \
// 0 1
//
//含义:1 OR (0 AND 1)
//结果:true OR false = true
TreeNode* root1 = new TreeNode(2);
root1->left = new TreeNode(1);
root1->right = new TreeNode(3);
root1->right->left = new TreeNode(0);
root1->right->right = new TreeNode(1);
cout << "Test 1 result: " << s.evaluateTree(root1) << endl; // 1
// 测试2:
// 3
// / \
// 1 0
//
//含义:1 AND 0
//结果:false
TreeNode* root2 = new TreeNode(3);
root2->left = new TreeNode(1);
root2->right = new TreeNode(0);
cout << "Test 2 result: " << s.evaluateTree(root2) << endl; // 0
// 测试3:
// 2
// / \
// 0 1
//
//含义:0 OR 1
//结果:true
TreeNode* root3 = new TreeNode(2);
root3->left = new TreeNode(0);
root3->right = new TreeNode(1);
cout << "Test 3 result: " << s.evaluateTree(root3) << endl; // 1
//测试4:
//单个叶子结点 0
TreeNode* root4 = new TreeNode(0);
cout << "Test 4 result: " << s.evaluateTree(root4) << endl; // 0
//测试5:
//单个叶子结点 1
TreeNode* root5 = new TreeNode(1);
cout << "Test 5 result: " << s.evaluateTree(root5) << endl; // 1
return 0;
}

3.求根节点到叶节点数字之和(OJ题)

算法思路:解法(dfs - 前序遍历):
前序遍历按照根节点、左子树、右子树的顺序遍历二叉树的所有节点,通常用于子节点的状态依赖于父节点状态的题目.
在前序遍历的过程中,我们可以往左右子树传递信息,并且在回溯时得到左右子树的返回值.递归函数可以帮我们完成两件事:
- 将父节点的数字与当前节点的信息整合到一起,计算出当前节点的数字,然后传递到下一层进行递归;
- 当遇到叶子节点的时候,就不再向下传递信息,而是将整合的结果向上一直回溯到根节点.
在递归结束时,根节点需要返回的值也就被更新为了整棵树的数字和.
算法流程:
递归函数设计:int dfs(TreeNode* root, int num)
- 返回值:当前子树计算的结果(数字和);
- 参数
num:递归过程中往下传递的信息(父节点的数字); - 函数作用:整合父节点的信息与当前节点的信息计算当前节点数字,并向下传递,在回溯时返回当前子树(当前节点作为子树根节点)数字和.
递归函数流程:
- 当遇到空节点的时候,说明这条路从根节点开始没有分支,返回0;
- 结合父节点传下的信息以及当前节点的
val,计算出当前节点数字sum; - 如果当前结点是叶子节点,直接返回整合后的结果
sum; - 如果当前结点不是叶子节点,将
sum传到左右子树中去,得到左右子树中节点路径的数字和,然后相加后返回结果. - 向下传递:从根节点开始,每往下走一层,就把之前的数字×10加上当前节点值,生成新的路径数字;
向上汇总:走到叶子节点时,得到一条完整路径的数字,回溯时把所有路径数字加起来,就是最终答案.

核心代码
cpp
class Solution
{
public:
//主函数:程序入口,调用递归函数,初始前缀和为 0
int sumNumbers(TreeNode* root) {
return dfs(root, 0);
}
//核心递归函数:DFS深度优先遍历
//root:当前遍历的节点
//presum:从【根节点】到【当前节点的父节点】的路径数字(前缀和)
int dfs(TreeNode* root, int presum)
{
//1.计算当前节点的路径值
//核心公式:前缀和 ×10(数字左移一位)+ 当前节点值 = 根到当前节点的完整数字
//例:presum=1 → ×10=10 + 当前值2 → 12
presum = presum * 10 + root->val;
//2.递归终止条件:遇到叶子节点
//叶子节点定义:左右孩子都为空
if (root->left == nullptr && root->right == nullptr)
//叶子节点:这条路径的数字计算完成,直接返回
return presum;
//3.递归遍历左右子树
//初始化结果变量,用于累加左右子树的路径和
int ret = 0;
//如果左子树存在,递归计算左子树所有路径和,并累加
if (root->left)
ret += dfs(root->left, presum);
//如果右子树存在,递归计算右子树所有路径和,并累加
if (root->right)
ret += dfs(root->right, presum);
//返回当前节点【所有子树的路径总和】
return ret;
}
};
完整测试代码
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)
{
return dfs(root, 0);
}
int dfs(TreeNode* root, int presum)
{
presum = presum * 10 + root->val;
if (root->left == nullptr && root->right == nullptr)
return presum;
int ret = 0;
if (root->left)
ret += dfs(root->left, presum);
if (root->right)
ret += dfs(root->right, presum);
return ret;
}
};
int main()
{
Solution s;
//测试1
// 1
// / \
// 2 3
//
//路径:
//1->2 = 12
//1->3 = 13
//总和 = 25
TreeNode* root1 = new TreeNode(1);
root1->left = new TreeNode(2);
root1->right = new TreeNode(3);
cout << "Test 1 result: " << s.sumNumbers(root1) << endl; // 25
//测试2
// 4
// / \
// 9 0
// / \
// 5 1
//
//路径:
//4->9->5 = 495
//4->9->1 = 491
//4->0 = 40
//总和 = 495 + 491 + 40 = 1026
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 << "Test 2 result: " << s.sumNumbers(root2) << endl; //1026
//测试3
//只有一个结点
// 7
//
//路径:
// 7
//总和 = 7
TreeNode* root3 = new TreeNode(7);
cout << "Test 3 result: " << s.sumNumbers(root3) << endl; // 7
//测试4
// 2
// /
// 3
// /
// 4
//
//路径:
//2->3->4 = 234
//总和 = 234
TreeNode* root4 = new TreeNode(2);
root4->left = new TreeNode(3);
root4->left->left = new TreeNode(4);
cout << "Test 4 result: " << s.sumNumbers(root4) << endl; // 234
//测试5
// 1
// / \
// 0 5
// / / \
// 3 6 7
//
//路径:
// 1->0->3 = 103
// 1->5->6 = 156
// 1->5->7 = 157
//总和 = 103 + 156 + 157 = 416
TreeNode* root5 = new TreeNode(1);
root5->left = new TreeNode(0);
root5->right = new TreeNode(5);
root5->left->left = new TreeNode(3);
root5->right->left = new TreeNode(6);
root5->right->right = new TreeNode(7);
cout << "Test 5 result: " << s.sumNumbers(root5) << endl; // 416
return 0;
}

4.二叉树剪枝(OJ题)

算法思路:解法(dfs - 后序遍历):
后序遍历按照左子树、右子树、根节点的顺序遍历二叉树的所有节点,通常用于父节点的状态依赖于子节点状态的题目.如果我们选择从上往下删除,我们需要收集左右子树的信息,这可能导致代码编写相对困难.然而,通过观察我们可以发现,如果我们先删除最底部的叶子节点,然后再处理删除后的节点,最终的结果并不会受到影响.因此,我们可以采用后序遍历的方式来解决这个问题.在后序遍历中,我们先处理左子树,然后处理右子树,最后再处理当前节点.在处理当前节点时,我们可以判断其是否为叶子节点且其值是否为0,如果满足条件,我们可以删除当前节点.需要注意的是,在删除叶子节点时,其父节点很可能会成为新的叶子节点.因此,在处理完子节点后,我们仍然需要处理当前节点.这也是为什么选择后序遍历的原因(后序遍历首先遍历到的一定是叶子节点).通过使用后序遍历,我们可以逐步删除叶子节点,并且保证删除后的节点仍然满足删除操作的要求.这样,我们可以较为方便地实现删除操作,而不会影响最终的结果.若在处理结束后所有叶子节点的值均为1,则所有子树均包含1,此时可以返回.
算法流程:
递归函数设计:void dfs(TreeNode*& root)
- 返回值:无;
- 参数:当前需要处理的节点;
- 函数作用:判断当前节点是否需要删除,若需要删除,则删除当前节点.
后序遍历的主要流程:
- 递归出口:当传入节点为空时,不做任何处理;
- 递归处理左子树;
- 递归处理右子树;
- 处理当前节点:判断该节点是否为叶子节点(即左右子节点均被删除,当前节点成为叶子节点),并且节点的值为0:
(1)如果是,就删除掉;
(2)如果不是,就不做任何处理.

核心代码
cpp
class Solution
{
public:
//主函数:二叉树剪枝,输入根节点,返回剪枝后的根节点
TreeNode* pruneTree(TreeNode* root)
{
//1.递归终止条件
//如果当前节点为空,直接返回空(没有节点需要剪)
if (root == nullptr)
return nullptr;
//2.后序遍历:先递归处理左右子树
//递归剪枝左子树,并把处理后的结果赋值给当前节点的左孩子
//关键:必须先处理子节点,再处理父节点(后序遍历)
root->left = pruneTree(root->left);
//递归剪枝右子树,并把处理后的结果赋值给当前节点的右孩子
root->right = pruneTree(root->right);
//3.剪枝核心判断:是否删除当前节点
//条件:当前节点是【叶子节点】(左右都空) + 节点值为0 → 剪掉这个节点
if (root->left == nullptr && root->right == nullptr && root->val == 0)
{
//工程中建议加上 delete root; 释放内存,防止内存泄漏
//将节点置为空,代表剪掉这个节点
root = nullptr;
}
//4. 返回处理后的当前节点
//要么返回原节点(没剪),要么返回空(已剪掉)
return root;
}
};
完整测试代码
cpp
#include <iostream>
#include <vector>
#include <queue>
#include <string>
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:
TreeNode* pruneTree(TreeNode* root)
{
if (root == nullptr)
return nullptr;
root->left = pruneTree(root->left);
root->right = pruneTree(root->right);
if (root->left == nullptr && root->right == nullptr && root->val == 0)
{
delete root;
return nullptr;
}
return root;
}
};
//根据层序数组建树,-1 表示 null
TreeNode* buildTree(const vector<int>& nums)
{
if (nums.empty() || nums[0] == -1)
return nullptr;
TreeNode* root = new TreeNode(nums[0]);
queue<TreeNode*> q;
q.push(root);
int i = 1;
while (!q.empty() && i < (int)nums.size())
{
TreeNode* cur = q.front();
q.pop();
if (i < (int)nums.size() && nums[i] != -1)
{
cur->left = new TreeNode(nums[i]);
q.push(cur->left);
}
i++;
if (i < (int)nums.size() && nums[i] != -1)
{
cur->right = new TreeNode(nums[i]);
q.push(cur->right);
}
i++;
}
return root;
}
//二叉树转成 LeetCode 风格层序数组字符串
vector<string> treeToVector(TreeNode* root)
{
vector<string> res;
if (root == nullptr)
return res;
queue<TreeNode*> q;
q.push(root);
while (!q.empty())
{
TreeNode* cur = q.front();
q.pop();
if (cur)
{
res.push_back(to_string(cur->val));
q.push(cur->left);
q.push(cur->right);
}
else
{
res.push_back("null");
}
}
//删除末尾多余的 null
while (!res.empty() && res.back() == "null")
res.pop_back();
return res;
}
// 打印数组
void printVector(const vector<string>& vec)
{
cout << "[";
for (int i = 0; i < (int)vec.size(); i++)
{
cout << vec[i];
if (i != (int)vec.size() - 1)
cout << ",";
}
cout << "]" << endl;
}
//释放整棵树
void deleteTree(TreeNode* root)
{
if (root == nullptr)
return;
deleteTree(root->left);
deleteTree(root->right);
delete root;
}
int main()
{
Solution s;
//测试1: [1,0,1,0,0,0,1]
vector<int> nums1 = {1, 0, 1, 0, 0, 0, 1};
TreeNode* root1 = buildTree(nums1);
cout << "Test 1 before prune: ";
printVector(treeToVector(root1));
root1 = s.pruneTree(root1);
cout << "Test 1 after prune: ";
printVector(treeToVector(root1));
deleteTree(root1);
cout << "---------------------" << endl;
//测试2: [1,0,0]
vector<int> nums2 = {1, 0, 0};
TreeNode* root2 = buildTree(nums2);
cout << "Test 2 before prune: ";
printVector(treeToVector(root2));
root2 = s.pruneTree(root2);
cout << "Test 2 after prune: ";
printVector(treeToVector(root2));
deleteTree(root2);
cout << "---------------------" << endl;
//测试3: [0]
vector<int> nums3 = {0};
TreeNode* root3 = buildTree(nums3);
cout << "Test 3 before prune: ";
printVector(treeToVector(root3));
root3 = s.pruneTree(root3);
cout << "Test 3 after prune: ";
printVector(treeToVector(root3));
deleteTree(root3);
cout << "---------------------" << endl;
//测试4: [1,1,0,null,null,1,0]
//这里用 -1 表示 null
vector<int> nums4 = {1, 1, 0, -1, -1, 1, 0};
TreeNode* root4 = buildTree(nums4);
cout << "Test 4 before prune: ";
printVector(treeToVector(root4));
root4 = s.pruneTree(root4);
cout << "Test 4 after prune: ";
printVector(treeToVector(root4));
deleteTree(root4);
return 0;
}

5.验证二叉搜索树(OJ题)

算法思路:解法(利用中序遍历):
后序遍历按照左子树、根节点、右子树的顺序遍历二叉树的所有节点,通常用于二叉搜索树相关题目.
如果一棵树是二叉搜索树,那么它的中序遍历的结果一定是一个严格递增的序列.因此,我们可以初始化一个无穷小的全区变量,用来记录中序遍历过程中的前驱结点.那么就可以在中序遍历的过程中,先判断是否和前驱结点构成递增序列,然后修改前驱结点为当前结点,传入下一层的递归中.
算法流程:
- 初始化一个全局的变量
prev,用来记录中序遍历过程中的前驱结点的val; - 中序遍历的递归函数中:
(1)设置递归出口:root == nullptr的时候,返回true;
(2)先递归判断左子树是否是二叉搜索树,用retleft标记;
(3)然后判断当前结点是否满足二叉搜索树的性质,用retcur标记:- 如果当前结点的
val大于prev,说明满足条件,retcur改为true; - 如果当前结点的
val小于等于prev,说明不满足条件,retcur改为false;
(4)最后递归判断右子树是否是二叉搜索树,用retright标记;
- 如果当前结点的
- 只有当
retleft、retcur和retright都是true的时候,才返回true.

核心代码
cpp
class Solution
{
//核心:记录中序遍历中【上一个节点的值】
//初始化为 long 类型的最小值,避免边界值冲突(如节点值为 int 最小值)
long prev = LONG_MIN;
public:
//主函数:递归验证是否为有效BST
bool isValidBST(TreeNode* root)
{
//1.递归终止条件
//空节点默认是有效的二叉搜索树
if (root == nullptr)
return true;
//2.中序遍历:递归遍历左子树
//中序顺序:左 → 根 → 右,先校验左子树
bool left = isValidBST(root->left);
//3.剪枝优化
//左子树已经无效,直接返回 false,无需继续遍历
if (left == false)
return false;
//4.验证当前节点:严格大于前驱节点
bool cur = false;
//BST 中序必须严格递增:当前节点值 > 上一个节点值
if (root->val > prev)
cur = true;
//剪枝:当前节点不满足递增,直接返回 false
if (cur == false)
return false;
//5.更新前驱节点
//把当前节点值,作为下一个节点的「前驱值」
prev = root->val;
//6.中序遍历:递归遍历右子树
bool right = isValidBST(root->right);
//7.最终结果
//左子树、当前节点、右子树 全部有效,才是真BST
return left && right && cur;
}
};
完整测试代码
cpp
#include <iostream>
#include <vector>
#include <queue>
#include <climits>
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
{
long prev = LONG_MIN;
public:
bool isValidBST(TreeNode* root)
{
if (root == nullptr)
return true;
bool left = isValidBST(root->left);
//剪枝
if (left == false)
return false;
bool cur = false;
if (root->val > prev)
cur = true;
//剪枝
if (cur == false)
return false;
prev = root->val;
bool right = isValidBST(root->right);
return left && right && cur;
}
};
//根据层序数组建树,-1 表示 null
TreeNode* buildTree(const vector<int>& nums)
{
if (nums.empty() || nums[0] == -1)
return nullptr;
TreeNode* root = new TreeNode(nums[0]);
queue<TreeNode*> q;
q.push(root);
int i = 1;
while (!q.empty() && i < (int)nums.size())
{
TreeNode* cur = q.front();
q.pop();
if (i < (int)nums.size() && nums[i] != -1)
{
cur->left = new TreeNode(nums[i]);
q.push(cur->left);
}
i++;
if (i < (int)nums.size() && nums[i] != -1)
{
cur->right = new TreeNode(nums[i]);
q.push(cur->right);
}
i++;
}
return root;
}
//中序遍历输出,便于观察
void inorder(TreeNode* root)
{
if (root == nullptr) return;
inorder(root->left);
cout << root->val << " ";
inorder(root->right);
}
//释放整棵树
void deleteTree(TreeNode* root)
{
if (root == nullptr) return;
deleteTree(root->left);
deleteTree(root->right);
delete root;
}
int main()
{
cout << boolalpha;
//测试1: [2,1,3]
vector<int> nums1 = {2, 1, 3};
TreeNode* root1 = buildTree(nums1);
cout << "Test 1 inorder: ";
inorder(root1);
cout << endl;
Solution s1;
cout << "Test 1 result: " << s1.isValidBST(root1) << endl;
cout << "------------------------" << endl;
deleteTree(root1);
//测试2: [5,1,4,null,null,3,6]
//用 -1 表示 null
vector<int> nums2 = {5, 1, 4, -1, -1, 3, 6};
TreeNode* root2 = buildTree(nums2);
cout << "Test 2 inorder: ";
inorder(root2);
cout << endl;
Solution s2;
cout << "Test 2 result: " << s2.isValidBST(root2) << endl;
cout << "------------------------" << endl;
deleteTree(root2);
//测试3: [1]
vector<int> nums3 = {1};
TreeNode* root3 = buildTree(nums3);
cout << "Test 3 inorder: ";
inorder(root3);
cout << endl;
Solution s3;
cout << "Test 3 result: " << s3.isValidBST(root3) << endl;
cout << "------------------------" << endl;
deleteTree(root3);
//测试4: [2,2,3]
vector<int> nums4 = {2, 2, 3};
TreeNode* root4 = buildTree(nums4);
cout << "Test 4 inorder: ";
inorder(root4);
cout << endl;
Solution s4;
cout << "Test 4 result: " << s4.isValidBST(root4) << endl;
cout << "------------------------" << endl;
deleteTree(root4);
//测试5: [10,5,15,2,12]
vector<int> nums5 = {10, 5, 15, 2, 12};
TreeNode* root5 = buildTree(nums5);
cout << "Test 5 inorder: ";
inorder(root5);
cout << endl;
Solution s5;
cout << "Test 5 result: " << s5.isValidBST(root5) << endl;
cout << "------------------------" << endl;
deleteTree(root5);
return 0;
}

6.二叉搜索树中的第k小的元素(OJ题)

算法思路:解法二(中序遍历 + 计数器剪枝):
上述解法不仅使用大量额外空间存储数据,并且会将所有的结点都遍历一遍.但是,我们可以根据中序遍历的过程,只需扫描前k个结点即可.因此,我们可以创建一个全局的计数器 count,将其初始化为 k,每遍历一个节点就将 count--.直到某次递归的时候,count 的值等于1,说明此时的结点就是我们要找的结果.
算法流程:
- 定义一个全局的变量
count,在主函数中初始化为k的值(不用全局也可以,当成参数传入递归过程中); - 递归函数的设计:
int dfs(TreeNode* root): 返回值为第k个结点;
递归函数流程(中序遍历):
- 递归出口:空节点直接返回
-1,说明没有找到; - 去左子树上查找结果,记为
retleft:
(1)如果retleft == -1,说明没找到,继续执行下面逻辑;
(2)如果retleft != -1,说明找到了,直接返回结果,无需执行下面代码(剪枝); - 如果左子树没找到,判断当前结点是否符合:如果符合,直接返回结果
- 如果当前结点不符合,去右子树上寻找结果.

核心代码
cpp
class Solution
{
//成员变量1:计数器,记录还需要找到第几个节点
int count;
//成员变量2:存储最终结果(第k小的节点值)
int ret;
public:
//主函数:入口,输入根节点和k,返回第k小的值
int kthSmallest(TreeNode* root, int k)
{
//初始化计数器:从k开始倒数
count = k;
//调用DFS中序遍历
dfs(root);
//返回找到的结果
return ret;
}
//核心DFS函数:中序遍历(左→根→右)
void dfs(TreeNode* root)
{
//1.递归终止+剪枝条件
//节点为空 || 已经找到答案(count=0),直接退出,不继续遍历
if (root == nullptr || count == 0)
return;
//2.中序遍历:递归左子树
//BST最小值在最左侧,优先遍历左子树
dfs(root->left);
//3.处理当前节点(核心计数)
//遍历到当前节点,计数器-1
count--;
//计数器减到0 → 当前节点就是【第k小的节点】
if (count == 0)
ret = root->val; // 把答案存入ret
//4.中序遍历:递归右子树
dfs(root->right);
}
};
完整测试代码
cpp
#include <iostream>
#include <vector>
#include <queue>
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
{
int count;
int ret;
public:
int kthSmallest(TreeNode* root, int k)
{
count = k;
ret = -1; //防止未初始化
dfs(root);
return ret;
}
void dfs(TreeNode* root)
{
if (root == nullptr || count == 0)
return;
dfs(root->left);
count--;
if (count == 0)
{
ret = root->val;
return;
}
dfs(root->right);
}
};
//根据层序数组建树,-1 表示 null
TreeNode* buildTree(const vector<int>& nums)
{
if (nums.empty() || nums[0] == -1)
return nullptr;
TreeNode* root = new TreeNode(nums[0]);
queue<TreeNode*> q;
q.push(root);
int i = 1;
while (!q.empty() && i < (int)nums.size())
{
TreeNode* cur = q.front();
q.pop();
if (i < (int)nums.size() && nums[i] != -1)
{
cur->left = new TreeNode(nums[i]);
q.push(cur->left);
}
i++;
if (i < (int)nums.size() && nums[i] != -1)
{
cur->right = new TreeNode(nums[i]);
q.push(cur->right);
}
i++;
}
return root;
}
//中序遍历输出
void inorder(TreeNode* root)
{
if (root == nullptr) return;
inorder(root->left);
cout << root->val << " ";
inorder(root->right);
}
//释放整棵树
void deleteTree(TreeNode* root)
{
if (root == nullptr) return;
deleteTree(root->left);
deleteTree(root->right);
delete root;
}
int main()
{
// ---------------- 测试1 ----------------
// [3,1,4,null,2]
// 中序:1 2 3 4
vector<int> nums1 = {3, 1, 4, -1, 2};
TreeNode* root1 = buildTree(nums1);
cout << "Test 1 inorder: ";
inorder(root1);
cout << endl;
Solution s1;
cout << "k = 1, result = " << s1.kthSmallest(root1, 1) << endl;
Solution s2;
cout << "k = 2, result = " << s2.kthSmallest(root1, 2) << endl;
Solution s3;
cout << "k = 3, result = " << s3.kthSmallest(root1, 3) << endl;
Solution s4;
cout << "k = 4, result = " << s4.kthSmallest(root1, 4) << endl;
cout << "------------------------" << endl;
deleteTree(root1);
// ---------------- 测试2 ----------------
// [5,3,6,2,4,null,null,1]
// 中序:1 2 3 4 5 6
vector<int> nums2 = {5, 3, 6, 2, 4, -1, -1, 1};
TreeNode* root2 = buildTree(nums2);
cout << "Test 2 inorder: ";
inorder(root2);
cout << endl;
Solution s5;
cout << "k = 3, result = " << s5.kthSmallest(root2, 3) << endl;
Solution s6;
cout << "k = 5, result = " << s6.kthSmallest(root2, 5) << endl;
cout << "------------------------" << endl;
deleteTree(root2);
// ---------------- 测试3 ----------------
// [7]
vector<int> nums3 = {7};
TreeNode* root3 = buildTree(nums3);
cout << "Test 3 inorder: ";
inorder(root3);
cout << endl;
Solution s7;
cout << "k = 1, result = " << s7.kthSmallest(root3, 1) << endl;
cout << "------------------------" << endl;
deleteTree(root3);
return 0;
}

7.二叉树的所有路径(OJ题)

算法思路:解法(回溯):
使用深度优先遍历(DFS)求解.
路径以字符串形式存储,从根节点开始遍历,每次遍历时将当前节点的值加入到路径中,如果该节点为叶子节点,将路径存储到结果中.否则,将 "->" 加入到路径中并递归遍历该节点的左右子树.
定义一个结果数组,进行递归。递归具体实现方法如下:
- 如果当前节点不为空,就将当前节点的值加入路径
path中,否则直接返回; - 判断当前节点是否为叶子节点,如果是,则将当前路径加入到所有路径的存储数组
paths中; - 否则,将当前节点值加上
"->"作为路径的分隔符,继续递归遍历当前节点的左右子节点. - 返回结果数组.
特别地,我们可以只使用一个字符串存储每个状态的字符串,在递归回溯的过程中,需要将路径中的当前节点移除,以回到上一个节点.
具体实现方法如下:
- 定义一个结果数组和一个路径数组.
- 从根节点开始递归,递归函数的参数为当前节点、结果数组和路径数组.
(1)如果当前节点为空,返回.
(2)将当前节点的值加入到路径数组中.
(3)如果当前节点为叶子节点,将路径数组中的所有元素拼接成字符串,并将该字符串存储到结果数组中.
(4)递归遍历当前节点的左子树.
(5)递归遍历当前节点的右子树.
(6)回溯,将路径数组中的最后一个元素移除,以返回到上一个节点. - 返回结果数组.

核心代码
cpp
class Solution
{
public:
//成员变量:存储最终所有路径的结果集
vector<string> ret;
//主函数:程序入口,返回所有路径
vector<string> binaryTreePaths(TreeNode* root)
{
string path; //初始化空路径字符串
//特殊情况:根节点为空,直接返回空结果
if (root == nullptr)
return ret;
//调用DFS递归遍历,开始拼接路径
dfs(root, path);
//返回所有路径结果
return ret;
}
//核心DFS递归函数:root=当前节点,path=从根到当前节点的路径(值传递)
void dfs(TreeNode* root, string path)
{
//1.将当前节点的值转为字符串,拼接到路径中
path += to_string(root->val);
//2.递归终止条件:当前节点是【叶子节点】(左右孩子都为空)
if (root->left == nullptr && root->right == nullptr)
{
//一条完整路径生成,加入结果集
ret.push_back(path);
return;
}
//3.非叶子节点:拼接路径分隔符 "->"
path += "->";
//4.递归遍历左子树(存在才遍历)
if (root->left)
dfs(root->left, path);
//5.递归遍历右子树(存在才遍历)
dfs(root->right, path);
}
};
完整测试代码
cpp
#include <iostream>
#include <vector>
#include <queue>
#include <string>
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:
vector<string> ret; //记录结果
vector<string> binaryTreePaths(TreeNode* root)
{
ret.clear(); //防止同一个对象重复调用时结果叠加
string path;
if (root == nullptr)
return ret;
dfs(root, path);
return ret;
}
void dfs(TreeNode* root, string path)
{
path += to_string(root->val);
if (root->left == nullptr && root->right == nullptr)
{
ret.push_back(path);
return;
}
path += "->";
if (root->left)
dfs(root->left, path);
if (root->right)
dfs(root->right, path);
}
};
//根据层序数组建树,-1 表示 null
TreeNode* buildTree(const vector<int>& nums)
{
if (nums.empty() || nums[0] == -1)
return nullptr;
TreeNode* root = new TreeNode(nums[0]);
queue<TreeNode*> q;
q.push(root);
int i = 1;
while (!q.empty() && i < (int)nums.size())
{
TreeNode* cur = q.front();
q.pop();
if (i < (int)nums.size() && nums[i] != -1)
{
cur->left = new TreeNode(nums[i]);
q.push(cur->left);
}
i++;
if (i < (int)nums.size() && nums[i] != -1)
{
cur->right = new TreeNode(nums[i]);
q.push(cur->right);
}
i++;
}
return root;
}
//打印 vector<string>
void printPaths(const vector<string>& paths)
{
cout << "[";
for (int i = 0; i < (int)paths.size(); i++)
{
cout << "\"" << paths[i] << "\"";
if (i != (int)paths.size() - 1)
cout << ", ";
}
cout << "]" << endl;
}
//释放整棵树
void deleteTree(TreeNode* root)
{
if (root == nullptr) return;
deleteTree(root->left);
deleteTree(root->right);
delete root;
}
int main()
{
// ---------------- 测试1 ----------------
// [1,2,3,-1,5]
// 对应 [1,2,3,null,5]
// 输出: ["1->2->5", "1->3"]
vector<int> nums1 = {1, 2, 3, -1, 5};
TreeNode* root1 = buildTree(nums1);
Solution s1;
vector<string> ans1 = s1.binaryTreePaths(root1);
cout << "Test 1 result: ";
printPaths(ans1);
deleteTree(root1);
cout << "------------------------" << endl;
// ---------------- 测试2 ----------------
// [1]
// 输出: ["1"]
vector<int> nums2 = {1};
TreeNode* root2 = buildTree(nums2);
Solution s2;
vector<string> ans2 = s2.binaryTreePaths(root2);
cout << "Test 2 result: ";
printPaths(ans2);
deleteTree(root2);
cout << "------------------------" << endl;
// ---------------- 测试3 ----------------
// [1,2,3,4,-1,5,6]
// 输出: ["1->2->4", "1->3->5", "1->3->6"]
vector<int> nums3 = {1, 2, 3, 4, -1, 5, 6};
TreeNode* root3 = buildTree(nums3);
Solution s3;
vector<string> ans3 = s3.binaryTreePaths(root3);
cout << "Test 3 result: ";
printPaths(ans3);
deleteTree(root3);
cout << "------------------------" << endl;
// ---------------- 测试4 ----------------
// 空树 []
// 输出: []
vector<int> nums4 = {};
TreeNode* root4 = buildTree(nums4);
Solution s4;
vector<string> ans4 = s4.binaryTreePaths(root4);
cout << "Test 4 result: ";
printPaths(ans4);
deleteTree(root4);
cout << "------------------------" << endl;
// ---------------- 测试5 ----------------
// [10,20,-1,30]
// 这其实会按层序构造成:
// 10
// /
// 20
// /
// 30
// 输出: ["10->20->30"]
vector<int> nums5 = {10, 20, -1, 30};
TreeNode* root5 = buildTree(nums5);
Solution s5;
vector<string> ans5 = s5.binaryTreePaths(root5);
cout << "Test 5 result: ";
printPaths(ans5);
deleteTree(root5);
return 0;
}


🚀真正的勇者不是流泪的人,而是含泪奔跑的人!
敬请期待下一篇文章内容:【递归、搜索与回溯算法】(穷举vs暴搜vs深搜vs回溯vs剪枝:一文讲清概念与用法)
每日心灵鸡汤:"日子总归是向前走的,人要学会和不属于自己的东西说再见"
学会放弃是件挺厉害的事情,放弃内耗自己,放下某个不自信的执念,放弃不切实际的幻想,放弃紧握着不撒手的过去,放弃不适合自己的生活.放弃这个词看着挺丧气,但想放弃就意味着新的开始,意味着你将收获新的东西.如果你总是抓住过去的事情,不放过那些受过的委屈和伤害耿耿于怀,以及那些根本无法改变的人和事上,无畏的消耗自己,会对任何事情提不起兴趣,没办法投入新生活.
日子总归是向前的,我们总要学会和不属于你的东西说再见.
