目录
二叉树中的深搜:
一、计算布尔二叉树的值
1.题目链接:2331. 计算布尔二叉树的值
2.题目描述:
3.解法(递归)
🍒算法思路:
🍒算法流程:
🍒算法代码:
二、求根节点到叶节点数字之和
1.题目链接:129. 求根节点到叶节点数字之和
2.题目描述:
3.解法(dfs - 前序遍历)
🍒算法思路:
🍒算法流程:
🍒算法代码:
三、二叉树剪枝
1.题目链接:814. 二叉树剪枝
2.题目描述:
3.解法(dfs - 后序遍历)
🍒算法思路:
🍒算法流程:
🍒算法代码:
四、验证二叉搜索树
1.题目链接:98. 验证二叉搜索树
2.题目描述:
3.解法(利用中序遍历)
🍒算法思路:
🍒算法流程:
🍒算法代码:
五、二叉搜索树中第 k 小的元素
1.题目链接:230. 二叉搜索树中第K小的元素
2.题目描述:
3.解法(中序遍历 + 计数器剪枝)
🍒算法思路:
🍒算法流程:
🍒算法代码:
六、二叉树的所有路径
1.题目链接:257. 二叉树的所有路径
2.题目描述:
3.解法(回溯)
🍒算法思路:
🍒算法代码:
深度优先遍历(DFS,全称为 Depth First Traversal),是树或者图这样的数据结构中常用的⼀种遍历算法。这个算法会尽可能深的搜索树或者图的分⽀,直到⼀条路径上的所有节点都被遍历完毕,然后再回溯到上⼀层,继续找⼀条路遍历。
在二叉树中,常见的深度优先遍历为:前序遍历、中序遍历以及后序遍历。
因为树的定义本⾝就是递归定义,因此采⽤递归的方法去实现树的三种遍历不仅容易理解而且代码很简洁。并且前中后序三种遍历的唯⼀区别就是访问根节点的时机不同,在做题的时候,选择⼀个适当的遍历顺序,对于算法的理解是⾮常有帮助的。
给你一棵 完整二叉树 的根,这棵树有以下特征:
- 叶子节点 要么值为
0
要么值为1
,其中0
表示False
,1
表示True
。- 非叶子节点 要么值为
2
要么值为3
,其中2
表示逻辑或OR
,3
表示逻辑与AND
。计算 一个节点的值方式如下:
- 如果节点是个叶子节点,那么节点的 值 为它本身,即
True
或者False
。- 否则,计算 两个孩子的节点值,然后将该节点的运算符对两个孩子值进行 运算 。
返回根节点
root
的布尔运算值。完整二叉树 是每个节点有
0
个或者2
个孩子的二叉树。叶子节点 是没有孩子的节点。
示例 1:
输入:root = [2,1,3,null,null,0,1] 输出:true 解释:上图展示了计算过程。 AND 与运算节点的值为 False AND True = False 。 OR 运算节点的值为 True OR False = True 。 根节点的值为 True ,所以我们返回 true 。示例 2:
输入:root = [0] 输出:false 解释:根节点是叶子节点,且值为 false,所以我们返回 false 。提示:
- 树中节点数目在
[1, 1000]
之间。0 <= Node.val <= 3
- 每个节点的孩子数为
0
或2
。- 叶子节点的值为
0
或1
。- 非叶子节点的值为
2
或3
。
本题可以被解释为:
1. 对于规模为 n 的问题,需要求得当前节点值。
2. 节点值不为 0 或 1 时,规模为 n 的问题可以被拆分为规模为 n-1 的子问题:
3. 当问题的规模变为 n=1 时,即叶子节点的值为 0 或 1,我们可以直接获取当前节点值为 0 或1。
递归函数设计:bool evaluateTree(TreeNode* root)
递归函数流程:
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; } };
给你一个二叉树的根节点
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
前序遍历按照根节点、左子树、右子树的顺序遍历二叉树的所有节点,通常用于子节点的状态依赖于父节点状态的题目。
在前序遍历的过程中,我们可以往左右子树传递信息,并且在回溯时得到左右子树的返回值。递归函数可以帮我们完成两件事:
递归函数设计:int dfs(TreeNode* root, int num)
递归函数流程:
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; } };
给你二叉树的根结点
root
,此外树的每个结点的值要么是0
,要么是1
。返回移除了所有不包含
1
的子树的原二叉树。节点
node
的子树为node
本身加上所有node
的后代。示例 1:
输入:root = [1,null,0,0,1] 输出:[1,null,0,null,1] 解释: 只有红色节点满足条件“所有不包含 1 的子树”。 右图为返回的答案。示例 2:
输入:root = [1,0,1,0,0,0,1] 输出:[1,null,1,null,1]示例 3:
输入:root = [1,1,0,1,1,0,1,0] 输出:[1,1,0,1,1,null,1]提示:
- 树中节点的数目在范围
[1, 200]
内Node.val
为0
或1
后序遍历按照左子树、右子树、根节点的顺序遍历二叉树的所有节点,通常用于父节点的状态依赖于子节点状态的题目。
如果我们选择从上往下删除,我们需要收集左右子树的信息,这可能导致代码编写相对困难。然而,通过观察我们可以发现,如果我们先删除最底部的叶子节点,然后再处理删除后的节点,最终的结果并不会受到影响。
因此,我们可以采用后序遍历的方式来解决这个问题。在后序遍历中,我们先处理左子树,然后处理右子树,最后再处理当前节点。在处理当前节点时,我们可以判断其是否为叶子节点且其值是否为 0,如果满足条件,我们可以删除当前节点。
递归函数设计:void dfs(TreeNode*& root)
1. 返回值:无;
2. 参数 :当前需要处理的节点;
3. 函数作用:判断当前节点是否需要删除,若需要删除,则删除当前节点。
后序遍历的主要流程:
1. 递归出口:当传入节点为空时,不做任何处理;
2. 递归处理左子树;
3. 递归处理右子树;
4. 处理当前节点:判断该节点是否为叶子节点(即左右子节点均被删除,当前节点成为叶子节点),并且节点的值为 0:
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;// 防止内存泄漏 root = nullptr; } return root; } };
给你一个二叉树的根节点
root
,判断其是否是一个有效的二叉搜索树。有效 二叉搜索树定义如下:
- 节点的左子树只包含 小于 当前节点的数。
- 节点的右子树只包含 大于 当前节点的数。
- 所有左子树和右子树自身必须也是二叉搜索树。
示例 1:
输入:root = [2,1,3] 输出:true示例 2:
输入:root = [5,1,4,null,null,3,6] 输出:false 解释:根节点的值是 5 ,但是右子节点的值是 4 。提示:
- 树中节点数目范围在
[1, 104]
内-231 <= Node.val <= 231 - 1
后序遍历按照左子树、根节点、右子树的顺序遍历二叉树的所有节点,通常用于二叉搜索树相关题目。
如果一棵树是二叉搜索树,那么它的中序遍历的结果一定是一个严格递增的序列。
因此,我们可以初始化一个无穷小的全局变量,用来记录中序遍历过程中的前驱结点。那么就可以在中序遍历的过程中,先判断是否和前驱结点构成递增序列,然后修改前驱结点为当前结点,传入下一层的递归中。
1. 初始化一个全局的变量 prev,用来记录中序遍历过程中的前驱结点的 val;
2. 中序遍历的递归函数中:
a. 设置递归出口:root == nullptr 的时候,返回 true;
b. 先递归判断左子树是否是二叉搜索树,用 retleft 标记;
c. 然后判断当前结点是否满足二叉搜索树的性质,用 retcur 标记:
d. 最后递归判断右子树是否是二叉搜索树,用 retright 标记;
3. 只有当 retleft、 retcur 和 retright 都是 true 的时候,才返回 true。
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; // 剪枝(如果当前节点为false,那后面的节点都不用去看了) if(cur == false) return false; prev = root->val; bool right = isValidBST(root->right); return left && cur && right; } };
给定一个二叉搜索树的根节点
root
,和一个整数k
,请你设计一个算法查找其中第k
小的元素(从 1 开始计数)。示例 1:
输入:root = [3,1,4,null,2], k = 1 输出:1示例 2:
输入:root = [5,3,6,2,4,null,null,1], k = 3 输出:3提示:
- 树中的节点数为
n
。1 <= k <= n <= 104
0 <= Node.val <= 104
创建一个全局的计数器 count,将其初始化为 k,每遍历一个节点就将 count--。直到某次递归的时候,count 的值等于 1,说明此时的结点就是我们要找的结果。
定义一个全局的变量 count,在主函数中初始化为 k 的值;
递归函数的设计:int dfs(TreeNode* root):
递归函数流程(中序遍历):
1. 递归出口:空节点直接返回 -1,说明没有找到;
2. 去左子树上查找结果,记为 retleft:
3. 如果左子树没找到,判断当前结点是否符合:
4. 如果当前结点不符合,去右子树上寻找结果
class Solution { int count; int ret;; public: int kthSmallest(TreeNode* root, int k) { count = k; dfs(root); return ret; } void dfs(TreeNode* root) { if(root == nullptr || count == 0) return; dfs(root->left); count--; if(count == 0) ret = root->val; dfs(root->right); } };
给你一个二叉树的根节点
root
,按 任意顺序 ,返回所有从根节点到叶子节点的路径。叶子节点 是指没有子节点的节点。
示例 1:
输入:root = [1,2,3,null,5] 输出:["1->2->5","1->3"]示例 2:
输入:root = [1] 输出:["1"]提示:
- 树中节点的数目在范围
[1, 100]
内-100 <= Node.val <= 100
使用深度优先遍历(DFS)求解。
路径以字符串形式存储,从根节点开始遍历,每次遍历时将当前节点的值加入到路径中,如果该节点为叶子节点,将路径存储到结果中。否则,将 "->" 加入到路径中并递归遍历该节点的左右子树。 定义一个结果数组,进行递归。递归具体实现方法如下:
特别地,我们可以只使用⼀个字符串存储每个状态的字符串,在递归回溯的过程中,需要将路径中的当前节点移除,以回到上一个节点。
具体实现方法如下:
1. 定义一个结果数组和一个路径数组。
2. 从根节点开始递归,递归函数的参数为当前节点、结果数组和路径数组。
3. 返回结果数组。
class Solution { public: vector ret;// 记录结果 vector binaryTreePaths(TreeNode* root) { string path; 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); } };