104.二叉树的最大深度
区别一下深度和高度
- 二叉树节点的深度:指从根节点到该节点的最长简单路径边的条数或节点数(取决于深度从0开始还是从1开始)
- 二叉树节点的高度:指从该节点到叶子节点的最长简单路径边的条数或节点数(取决于高度从0开始还是从1开始)
而根节点的高度就是二叉树的最大深度,也称为树的高度
我们通过递归求的根节点高度来求的二叉树的高度
首先思考递归调用能干什么事:这个递归调用能够得到树的高度
然后递归三部曲
确定参数和返回值:这个递归调用能够得到树的高度,总要传进去一个根节点表示这棵树,返回一个高度
int maxDepth(TreeNode* root)
确定终止条件:这个递归调用能够能够得到树的高度,当传入的节点为空,空树,直接返回高度0
if (root == nullptr) return 0;
确定单层递归的逻辑:这个递归调用能够能够得到树的高度,很容易想到一棵树的高度或为其左子树高度 + 1,或为其右子树高度 + 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:
int maxDepth(TreeNode* root) { // 虽然命名为maxDepth,但是可以理解为这个函数功能为获取树的高度
if (root == nullptr) return 0;
int leftDepth = maxDepth(root -> left); // 获取左子树高度
int rightDepth = maxDepth(root -> right); // 获取右子树高度
return max(leftDepth + 1, rightDepth + 1);
}
};
此外,该函数先获取了左子树高度,再获取了右子树高度,然后返回该函数(处理根节点的函数)比较左子树高度和右子树高度。这种其实就是一种后序遍历的过程(左右根),在处理根节点的时候需要用到遍历左子树和右子树的结果,这是后序遍历的一大优势
559.n叉树的最大深度
同样的思路,求某棵树的高度等于其所有子树中高度的最大值 + 1
求子树高度可以递归调用本身
/*
// Definition for a Node.
class Node {
public:
int val;
vector<Node*> children;
Node() {}
Node(int _val) {
val = _val;
}
Node(int _val, vector<Node*> _children) {
val = _val;
children = _children;
}
};
*/
class Solution {
public:
int maxDepth(Node* root) {
if (root == nullptr) return 0;
int maxChildrenDepth = 0;
for (auto & node : root -> children) {
int childrenDepth = maxDepth(node);
maxChildrenDepth = max(childrenDepth, maxChildrenDepth);
} // 获取所有子树高度中的最大值
return maxChildrenDepth + 1; // 后序处理
}
};
111.二叉树的最小深度
如果是按照上一道题的解法:定义递归函数作用为得到二叉树的最小深度
很容易写出以下代码
class Solution {
public:
int minDepth(TreeNode* root) {
if (root == nullptr) return 0;
int leftDepth = minDepth(root -> left);
int rightDepth = minDepth(root -> right);
return min(leftDepth + 1, rightDepth + 1);
}
};
确定递归函数的作用是获取二叉树最小深度,然后确定终止条件,然后处理逻辑,看似没啥问题
但是注意题目描述:最小深度是从根节点到最近叶子节点的最短路径上的节点数量
如图,这棵树的最小深度应该是3.如果用上面的代码,在遍历到根节点的时候,发现根的左子树为空,左子树的最小深度为0,接下来min一定是取遍历左子树得到的结果0 + 1,返回就是1了。不符合题意
根本原因在于当遇到子树为空的时候,min可能会做出错误的返回
怎么办呢?例如,还是上图,左子树为空,我们希望不要返回左子树最小深度0 + 1,而返回右子树的最小深度 + 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:
int minDepth(TreeNode* root) {
if (root == nullptr) return 0;
int leftDepth = minDepth(root -> left);
int rightDepth = minDepth(root -> right);
if (root -> left == nullptr) // 处理子树为空的情况
return rightDepth + 1;
if (root -> right == nullptr)
return leftDepth + 1;
return min(leftDepth + 1, rightDepth + 1);
}
};
222.完全二叉树的节点个数
一开始想到了层序遍历,直接套用层序遍历的模板(Day15)
/**
* 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) {
queue<TreeNode*> que;
int res = 0;
if (root != nullptr) que.push(root);
while (!que.empty()) { // 准备一层一层遍历
int size = que.size(); // 一些准备工作
for (int i = 0; i < size; ++i) { // 遍历该层各个节点
TreeNode * node = que.front();
que.pop();
if (node -> left) que.push(node -> left);
if (node -> right) que.push(node -> right); // 遍历节点:出队列,并入队列其子
}
res += size; // 将该层遍历的节点个数加入res
}
return res;
}
};
又想到了用递归:明确递归函数的作用为返回树的节点个数,然后递归三部曲,后序遍历代码如下
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; // 树的节点个数为左子树节点数+右子树节点数+1(1代表根节点本身)
}
};
这两种方式将整棵树每个节点遍历了一遍,时间复杂度为O(n)
但是,上面两种方式, 都没用到完全二叉树这个条件
如何利用?
完全二叉树的两种情况:
- 满二叉树,可以直接用 来计算
- 最后一层叶子节点没有满:分别递归左孩子,和右孩子,递归到某一深度一定会有左孩子或者右孩子为满二叉树,然后再套公式
我们的思路其实就是在上面递归的基础上增加终止情况
上面的递归中,终止条件设置为:遇到根为空,直接返回0
我们增加终止情况:如果树为满二叉树,也能直接返回带入公式的结果
这里就涉及到一个判定满二叉树及获取满二叉树深度的方法
当一个树为满二叉树,当且仅当递归向左遍历的深度等于递归向右遍历的深度(即将上图两根红线的长度应该相等)
因此,我们代码的逻辑如下
- 终止条件:root为空直接返回0
- 递归向左遍历到底,获得深度
- 递归向右遍历到底,获得深度
- 如果上面两个深度相等,说明是满二叉树,终止条件:返回带入公式计算出的节点个数
- 递归获取左孩子节点个数
- 递归获取右孩子节点个数
- 返回左孩子节点个数 + 右孩子节点个数 + 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;
}
}
回顾总结
深度与高度的区别与联系
巩固一下明确递归函数的作用及递归三部曲
以后做完题目后要回顾一下,本题用的是哪种遍历方式(层序还是深度优先,深度优先的话是哪种顺序)
后序遍历的特点:在遍历根节点的时候已经有了遍历左右子树的返回结果