目录
①力扣2331. 计算布尔二叉树的值
难度 简单
给你一棵 完整二叉树 的根,这棵树有以下特征:
- 叶子节点 要么值为
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
。
/**
* 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:
bool evaluateTree(TreeNode* root) {
}
};
解析代码
通过题目拆分成子问题,使用递归:
计算当前结果就要知道左子树的结果和右子树的结果,遇到叶子结点返回。
/**
* 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:
bool evaluateTree(TreeNode* root) {
if(root->left == nullptr)
return root->val; // 0/1
bool left = evaluateTree(root->left);
bool right = evaluateTree(root->right);
return root->val == 2 ? (left || right) : (left && right); // 因为是bool所以可以用下面的逻辑与和或
// return root->val == 2 ? (left | right) : (left & right);
}
};
②力扣129. 求根节点到叶节点数字之和
难度 中等
给你一个二叉树的根节点 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
/**
* 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 sumNumbers(TreeNode* root) {
}
};
解析代码
在前序遍历的过程中,我们可以往左右子树传递信息,并且在回溯时得到左右子树的返回值。递归函数可以帮我们完成两件事:
- 将父节点的数字与当前节点的信息整合到一起,计算出当前节点的数字,然后传递到下一层进行递归。
- 当遇到叶子节点的时候,就不再向下传递信息,而是将整合的结果向上一直回溯到根节点。 在递归结束时,根节点需要返回的值也就被更新为了整棵树的数字和。
递归函数设计:int dfs(TreeNode* root, int num)
- 返回值:当前子树计算的结果(数字和)
- 参数 num:递归过程中往下传递的信息(父节点的数字)
- 函数作用:整合父节点的信息与当前节点的信息计算当前节点数字,并向下传递,在回溯时返回当前子树(当前节点作为子树根节点)数字和。
- 函数体也就是dfs的代码,要把当前值传下去,递归下面的①②③④步:
/**
* 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 sumNumbers(TreeNode* root) {
return dfs(root, 0);
}
int dfs(TreeNode* root, int sum)
{
sum = sum*10 + root->val;
if(root->left == nullptr && root->right == nullptr)
return sum;
int ret = 0;
if(root->left) // 如果左子树不为空
ret += dfs(root->left, sum);
if(root->right)
ret += dfs(root->right, sum);
return ret;
}
};
③力扣814. 二叉树剪枝
难度 中等
给你二叉树的根结点 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
/**
* 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:
TreeNode* pruneTree(TreeNode* root) {
}
};
解析代码
题意就是删除所有元素只有0的子树,解题思路就是递归+后序遍历:
如果选择从上往下删除,需要收集左右子树的信息,这可能导致代码编写相对困难。然而通过观察我们可以发现,如果我们先删除最底部的叶子节点,然后再处理删除后的节点,最终的结果并不会受到影响。
因此可以采用后序遍历的方式来解决这个问题。在后序遍历中,先处理左子树,然后处理右子树,最后再处理当前节点。在处理当前节点时,判断其是否为叶子节点且其值是否为 0, 如果满足条件,可以删除当前节点。
- 需要注意的是,在删除叶子节点时,其父节点很可能会成为新的叶子节点。因此,在处理完子节点后,仍然需要处理当前节点。这也是为什么选择后序遍历的原因(后序遍历首先遍历到的一定是叶子节点)。
- 通过使用后序遍历,可以逐步删除叶子节点,并且保证删除后的节点仍然满足删除操作的要 求。这样,我们可以较为方便地实现删除操作,而不会影响最终的结果。
- 若在处理结束后所有叶子节点的值均为 1,则所有子树均包含 1,此时可以返回。
相信递归一定能把左右子树都剪枝了,出口是碰到叶子结点,函数体是如果这个结点的左结点和右结点都为空,并且值为0,就剪掉。
/**
* 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:
TreeNode* pruneTree(TreeNode* root) {
/*
题意就是删除所有元素只有0的子树,解题思路就是递归+后序遍历:
相信递归一定能把左右子树都剪枝了,出口是碰到叶子结点,
函数体是如果这个结点的左结点和右结点都为空,并且值为0,就剪掉。
*/
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; // 可加可不加,面试时可以问结点是不是new出来的,不然会报错
root = nullptr;
}
return root; // 返回当前结点,和遇到叶子统一步骤:返回
}
};
④力扣98. 验证二叉搜索树
难度 中等
给你一个二叉树的根节点 root
,判断其是否是一个有效的二叉搜索树。
有效 二叉搜索树定义如下:
- 节点的左子树只包含 小于 当前节点的数。
- 节点的右子树只包含 大于 当前节点的数。
- 所有左子树和右子树自身必须也是二叉搜索树。
示例 1:
输入:root = [2,1,3] 输出:true
示例 2:
输入:root = [5,1,4,null,null,3,6] 输出:false 解释:根节点的值是 5 ,但是右子节点的值是 4 。
提示:
- 树中节点数目范围在
[1, 10^4]
内 -2^31 <= Node.val <= 2^31 - 1
/**
* 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:
}
};
解析代码
一颗二叉搜索树中序遍历一定是严格递增的。因此可以初始化一个无穷小的全区变量,用来记录中序遍历过程中的前驱结点。那么就可以在中序遍历的过程中,先判断是否和前驱结点构成递增序列,然后修改前驱结点为当前结点,传入下一层的递归中。
/**
* 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 {
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 flag = false;
if(root->val > prev) // 判断自己,大于prev就是ture
{
flag = true;
}
if(flag == false) // 剪枝
return false;
prev = root->val;
bool right = isValidBST(root->right); // 判断右子树
return left && right && flag; // 全都是true才是true
}
};
⑤力扣230. 二叉搜索树中第K小的元素
难度 中等
给定一个二叉搜索树的根节点 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 <= 10^4
0 <= Node.val <= 10^4
进阶:如果二叉搜索树经常被修改(插入/删除操作)并且你需要频繁地查找第 k
小的值,你将如何优化算法?
/**
* 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 kthSmallest(TreeNode* root, int k) {
};
解析代码
思路就是中序遍历到第K个结点,然后返回这个结点的值,可以弄两个全局变量来简化代码。
/**
* 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 {
int cnt = 0, ret = 0;
public:
int kthSmallest(TreeNode* root, int k) {
cnt = k;
dfs(root);
return ret;
}
void dfs(TreeNode* root)
{
if(root == nullptr || cnt == 0)
return;
dfs(root->left); // 遍历左子树
if(--cnt == 0) // 判断
ret = root->val;
dfs(root->right); // 遍历右子树
}
};
⑥力扣257. 二叉树的所有路径
难度 简单
给你一个二叉树的根节点 root
,按 任意顺序 ,返回所有从根节点到叶子节点的路径。
叶子节点 是指没有子节点的节点。
示例 1:
输入:root = [1,2,3,null,5] 输出:["1->2->5","1->3"]
示例 2:
输入:root = [1] 输出:["1"]
提示:
- 树中节点的数目在范围
[1, 100]
内 -100 <= Node.val <= 100
/**
* 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:
vector<string> binaryTreePaths(TreeNode* root) {
}
};
解析代码
(这些简单题都是为了重点突出:处处有回溯、剪枝的代码很简单、全局变量的便利、出现了回溯才要想到恢复现场的概念)
思路就是遍历到叶子结点就push当前路径, 到最后剪枝,不用写函数出口。
路径以字符串形式存储,从根节点开始遍历,每次遍历时将当前节点的值加⼊到路径中,如果该节点为叶子节点,将路径存储到结果中。否则,将 "->" 加⼊到路径中并递归遍历该节点的左右子树。 定义一个结果数组,进行递归。
递归具体实现方法如下:
- 如果当前节点不为空,就将当前节点的值加入路径 path 中,否则直接返回
- 判断当前节点是否为叶子节点,如果是,则将当前路径加入到所有路径的存储数组 path 中
- 否则,将当前节点值加上 "->" 作为路径的分隔符,继续递归遍历当前节点的左右子节点。
- 返回结果数组。
可以只使用一个字符串存储每个状态的字符串,在递归回溯的过程中,需要将路径中的当前节点移除,以回到上一个节点。
/**
* 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 {
vector<string> ret;
public:
vector<string> binaryTreePaths(TreeNode* root) {
dfs(root, "");
return ret;
}
void dfs(TreeNode* root, string path)
{
path += to_string(root->val); // 不可能传空root进来
if(root->left == nullptr && root->right == nullptr)
{
ret.push_back(path); // 是叶子结点就push当前路径
}
else // 不是叶子结点就先加上箭头
{
path += "->";
}
if(root->left != nullptr) // 剪枝,此时不用写函数出口
dfs(root->left, path);
if(root->right != nullptr)
dfs(root->right, path);
}
};
本篇完。
下一篇是分治快排类型的OJ。
(穿越回来复习顺便贴个下篇链接:Offer必备算法09_分治快排_四道力扣OJ(快排三路划分)-优快云博客)