Leetcode 222. 完全二叉树的节点个数
题目描述
给你一棵 完全二叉树 的根节点 root
,求出该树的节点个数。
完全二叉树 的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h
层,则该层包含 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
- 题目数据保证输入的树是 完全二叉树
解题思路
普通二叉树的求法与利用完全二叉树性质的求法
int leftNum = getNodesNum(cur->left); // 左
int rightNum = getNodesNum(cur->right); // 右
int treeNum = leftNum + rightNum + 1; // 中
return treeNum;
普通二叉树:这道题目的递归法和求二叉树的深度写法类似,记录遍历的节点数量就可以了。递归遍历的顺序依然是后序(左右中)。
递归三部曲
1.确定递归函数的参数和返回值:参数就是传入树的根节点,返回就返回以该节点为根节点二叉树的节点数量,所以返回值为int类型。
int getNodesNum(TreeNode* cur) {
2.确定终止条件:如果为空节点的话,就返回0,表示节点数为0。
if (cur == NULL) return 0;
3.确定单层递归的逻辑:先求它的左子树的节点数量,再求右子树的节点数量,最后取总和再加一 (加1是因为算上当前中间节点)就是目前节点为根节点的节点数量。
int leftNum = getNodesNum(cur->left); // 左
int rightNum = getNodesNum(cur->right); // 右
int treeNum = leftNum + rightNum + 1; // 中
return treeNum;
完整代码
class Solution {
private:
int getNodesNum(TreeNode* cur) {
if (cur == NULL) return 0;
int leftNum = getNodesNum(cur->left); // 左
int rightNum = getNodesNum(cur->right); // 右
int treeNum = leftNum + rightNum + 1; // 中
return treeNum;
}
public:
int countNodes(TreeNode* root) {
return getNodesNum(root);
}
};
完全二叉树:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2^(h-1) 个节点。
完全二叉树只有两种情况,情况一:就是满二叉树,情况二:最后一层叶子节点没有满。
对于情况一,可以直接用 2^树深度 - 1 来计算,注意这里根节点深度为1。
对于情况二,分别递归左孩子,和右孩子,递归到某一深度一定会有左孩子或者右孩子为满二叉树,然后依然可以按照情况1来计算。
完整代码
class Solution {
public:
int countNodes(TreeNode* root) {
if (root == nullptr) return 0;
TreeNode* left = root->left;
TreeNode* right = root->right;
int leftDepth = 0, rightDepth = 0; // 这里初始为0是有目的的,为了下面求指数方便
while (left) { // 求左子树深度
left = left->left;
leftDepth++;
}
while (right) { // 求右子树深度
right = right->right;
rightDepth++;
}
if (leftDepth == rightDepth) {
return (2 << leftDepth) - 1; // 注意(2<<1) 相当于2^2,所以leftDepth初始为0
}
return countNodes(root->left) + countNodes(root->right) + 1;
}
};
Leetcode
题目描述
给定一个二叉树,判断它是否是
平衡二叉树
示例 1:
输入:root = [3,9,20,null,null,15,7] 输出:true
示例 2:
输入:root = [1,2,2,3,3,null,null,4,4] 输出:false
示例 3:8
输入:root = [] 输出:true
解题思路
一棵高度平衡二叉树定义为:一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。
求深度可以从上到下去查 所以需要前序遍历(中左右),而高度只能从下到上去查,所以只能后序遍历(左右中)
class Solution {
public:
// 返回以该节点为根节点的二叉树的高度,如果不是平衡二叉树了则返回-1
int getHeight(TreeNode* node) {
if (node == NULL) {
return 0;
}
int leftHeight = getHeight(node->left);
if (leftHeight == -1) return -1;
int rightHeight = getHeight(node->right);
if (rightHeight == -1) return -1;
return abs(leftHeight - rightHeight) > 1 ? -1 : 1 + max(leftHeight, rightHeight);
}
bool isBalanced(TreeNode* root) {
return getHeight(root) == -1 ? false : true;
}
};
Leetcode
题目描述
给你一个二叉树的根节点 root
,按 任意顺序 ,返回所有从根节点到叶子节点的路径。
叶子节点 是指没有子节点的节点。
示例 1:
输入:root = [1,2,3,null,5] 输出:["1->2->5","1->3"]
示例 2:
输入:root = [1] 输出:["1"]
提示:
- 树中节点的数目在范围
[1, 100]
内 -100 <= Node.val <= 100
解题思路
这道题目是秋从根节点到叶子节点的所有路径,所以需要使用前序遍历,这样才方便让父节点指向子节点,找到对应的路径。因为要记录路径,所以还必须使用到回溯的过程,回退一条路径进入另一条路径。
递归三部曲
1.确定递归函数参数和返回值
传入二叉树根节点的同时还需要使用两个数组发呢别记录每一条路径path和最终结果集result,且不需要返回值
void traversal(TreeNode* cur, vector<int>& path, vector<string>& result)
2.确定递归终止条件
因为本题要找到叶子节点,就开始结束的处理逻辑了(把路径放进result里)。那么什么时候算是找到了叶子节点? 是当 cur不为空,其左右孩子都为空的时候,就找到叶子节点
if (cur->left == NULL && cur->right == NULL) {
终止处理逻辑
}
这里使用vector 结构path来记录路径,所以要把vector 结构的path转为string格式,再把这个string 放进 result里。那么为什么使用了vector 结构来记录路径呢? 因为在下面处理单层递归逻辑的时候,要做回溯,使用vector方便来做回溯。
if (cur->left == NULL && cur->right == NULL) { // 遇到叶子节点
// 定义一个字符串sPath,用于构建当前路径的字符串表示
string sPath;
// 遍历路径中的所有节点,除了最后一个节点,因为最后一个节点不需要箭头。
for (int i = 0; i < path.size() - 1; i++) { // 将path里记录的路径转为string格式
sPath += to_string(path[i]);
sPath += "->";
}
sPath += to_string(path[path.size() - 1]); // 记录最后一个节点(叶子节点)
result.push_back(sPath); // 收集一个路径
return;
}
3.确定单层递归逻辑
因为要先处理中间节点,中间节点是需要记录的路径上的节点,先放进path中,然后就是处理左右节点,如果cur为空 就不再递归,然后还需要做回溯操作,当到达二叉树中一条边的尽头时,还要删除节点,然后从才能加入新的节点。
if (cur->left) {
traversal(cur->left, path, result);
path.pop_back(); // 回溯 从路径中移除当前节点的值,因为要返回到当前节点的父节点
}
if (cur->right) {
traversal(cur->right, path, result);
path.pop_back(); // 回溯
}
完整代码
class Solution {
private:
void traversal(TreeNode* cur, vector<int>& path, vector<string>& result) {
path.push_back(cur->val); // 中,中为什么写在这里,因为最后一个节点也要加入到path中
// 这才到了叶子节点
if (cur->left == NULL && cur->right == NULL) {
string sPath;
for (int i = 0; i < path.size() - 1; i++) {
sPath += to_string(path[i]);
sPath += "->";
}
sPath += to_string(path[path.size() - 1]);
result.push_back(sPath);
return;
}
if (cur->left) { // 左
traversal(cur->left, path, result);
path.pop_back(); // 回溯
}
if (cur->right) { // 右
traversal(cur->right, path, result);
path.pop_back(); // 回溯
}
}
public:
vector<string> binaryTreePaths(TreeNode* root) {
vector<string> result;
vector<int> path;
if (root == NULL) return result;
traversal(root, path, result);
return result;
}
};
这里的回溯流程,不太好理解
例如:
1
/ \
2 3
/ \
4 5
我们的目标是找到从根节点到所有叶子节点的路径。按照这个树,路径应该是:
1 -> 2 -> 4
1 -> 2 -> 5
1 -> 3
现在,逐步描述代码流程:
1. 初始调用
- 调用
binaryTreePaths(root)
,根节点root
是1
。 - 初始化
result
为空字符串向量,用于存储最终的路径。 - 初始化
path
为空整数向量,用于临时存储当前路径。
2. 第一次递归
traversal(1, path, result)
path.push_back(1)
,path
变为[1]
- 检查节点
1
,它有左子节点2
和右子节点3
。
3. 遍历左子树
traversal(2, path, result)
path.push_back(2)
,path
变为[1, 2]
- 检查节点
2
,它有左子节点4
和右子节点5
。
4. 遍历左子树的左子节点
traversal(4, path, result)
path.push_back(4)
,path
变为[1, 2, 4]
- 检查节点
4
,它没有子节点,是一个叶子节点。 - 构建路径字符串
"1->2->4"
,添加到result
中。 path.pop_back()
,回溯,path
变为[1, 2]
5. 遍历左子树的右子节点
traversal(5, path, result)
path.push_back(5)
,path
变为[1, 2, 5]
- 检查节点
5
,它没有子节点,是一个叶子节点。 - 构建路径字符串
"1->2->5"
,添加到result
中。 path.pop_back()
,回溯,path
变为[1, 2]
6. 回溯到根节点的左子节点
path.pop_back()
,回溯,path
变为[1]
7. 遍历右子树
traversal(3, path, result)
path.push_back(3)
,path
变为[1, 3]
- 检查节点
3
,它没有子节点,是一个叶子节点。 - 构建路径字符串
"1->3"
,添加到result
中。 path.pop_back()
,回溯,path
变为[1]
8. 回溯到根节点
path.pop_back()
,回溯,path
变为空
9. 返回结果
binaryTreePaths
函数返回result
,它包含了所有路径:["1->2->4", "1->2->5", "1->3"]
。
Leetcode 404. 左叶子之和
题目描述
给定二叉树的根节点 root
,返回所有左叶子之和。
示例 1:
输入: root = [3,9,20,null,null,15,7] 输出: 24 解释: 在这个二叉树中,有两个左叶子,分别是 9 和 15,所以返回 24
示例 2:
输入: root = [1] 输出: 0
解题思路
首先要注意是判断左叶子,不是二叉树左侧节点,所以不要上来想着层序遍历。因为题目中其实没有说清楚左叶子究竟是什么节点,那么我来给出左叶子的明确定义:节点A的左孩子不为空,且左孩子的左右孩子都为空(说明是叶子节点),那么A节点的左孩子为左叶子节点
那么判断当前节点是不是左叶子是无法判断的,必须要通过节点的父节点来判断其左孩子是不是左叶子。如果该节点的左节点不为空,该节点的左节点的左节点为空,该节点的左节点的右节点为空,则找到了一个左叶子,判断代码如下:
if (node->left != NULL && node->left->left == NULL && node->left->right == NULL) {
左叶子节点处理逻辑
}
递归三部曲:
1.确定递归函数的参数和返回值
判断一个树的左叶子节点之和,那么一定要传入树的根节点,递归函数的返回值为数值之和,所以为int
2.确定终止条件
如果遍历到空节点,那么左叶子值一定是0
if (root == NULL) return 0;
注意,只有当前遍历的节点是父节点,才能判断其子节点是不是左叶子。 所以如果当前遍历的节点是叶子节点,那其左叶子也必定是0,那么终止条件为:
if (root == NULL) return 0;
if (root->left == NULL && root->right== NULL) return 0; //其实这个也可以不写,如果不写不影响结果,但就会让递归多进行了一层。
3.确定单层递归的逻辑
当遇到左叶子节点的时候,记录数值,然后通过递归求取左子树左叶子之和,和 右子树左叶子之和,相加便是整个树的左叶子之和。
代码如下:
int leftValue = sumOfLeftLeaves(root->left); // 左
if (root->left && !root->left->left && !root->left->right) {
leftValue = root->left->val;
}
int rightValue = sumOfLeftLeaves(root->right); // 右
int sum = leftValue + rightValue; // 中
return sum;
整体递归代码如下:
class Solution {
public:
int sumOfLeftLeaves(TreeNode* root) {
if (root == NULL) return 0;
if (root->left == NULL && root->right== NULL) return 0;
int leftValue = sumOfLeftLeaves(root->left); // 左
if (root->left && !root->left->left && !root->left->right) { // 左子树就是一个左叶子的情况
leftValue = root->left->val;
}
int rightValue = sumOfLeftLeaves(root->right); // 右
int sum = leftValue + rightValue; // 中
return sum;
}
};
例如:
3
/ \
9 20
/ \
15 7
在这个树中,有两个左叶子节点:9
和15
。所以,我们期望的结果是9 + 15 = 24
。
1. 初始调用
- 调用
sumOfLeftLeaves(root)
,根节点root
是3
。 - 判断根节点是否为空,不是,则继续。
- 判断根节点是否是叶子节点,不是,则继续。
2. 递归到左子树
sumOfLeftLeaves(root->left)
,左子节点是9
。- 因为
9
是叶子节点,且是左子树,所以它的值9
被返回。
3. 递归到右子树
sumOfLeftLeaves(root->right)
,右子节点是20
。- 递归到
20
的左子树,15
。15
是叶子节点,且是左子树,所以它的值15
被返回。
- 递归到
20
的右子树,7
。7
不是叶子节点,返回0
。
4. 计算右子树(左叶子节点)的值
rightValue = sumOfLeftLeaves(root->right)
,计算右子树的值。15
(左叶子节点)+0
(右子树没有左叶子节点)=15
。
5. 计算总和
int sum = leftValue + rightValue
,计算总和。9
(左子树的左叶子节点)+15
(右子树的左叶子节点)=24
。
6. 返回结果
- 返回计算出的总和
24
。