104.二叉树的最大深度
给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明: 叶子节点是指没有子节点的节点。
示例: 给定二叉树 [3,9,20,null,null,15,7],
返回它的最大深度 3。
tip:二叉树中每一个结点都有自己的深度和高度。
二叉树的深度:二叉树中任意节点到根节点的距离(记住这个定义,很重要,不理解深度都是因为没有记住这个的定义;深度这个东西不需要你去理解,你只要记住定义就可以)。
二叉树的高度:二叉树中任意结点到叶子节点的距离(后序遍历 )。
求高度是从下往上计数(叶子节点是1,然后依次往上递增);求深度是从上往下计数(根节点为1,叶子结点最大)。
(1)求高度:
关键点:二叉树都是从上往下去遍历的,那如何求高度呢?——因为求高度要求我们从下往上遍历。
解决办法:通过“左右中”的遍历顺序,把处理“中”的逻辑放在最后,这样子就可以把左右叶子结点的高度返回给“中”,根节点知道了他的孩子的高度,然后再加一个1,就是中间结点的高度了,这样依次计算,最后就能计算出根节点的高度。——》后序遍历
(2)求深度:
使用前序遍历,把中间结点的深度返回给左右叶子结点。
1.递归法
(1)后续遍历--求高度
这道题的解题关键:根节点的高度就是这颗二叉树的“最大”深度!——所以我们用后序遍历就去根节点的高度就可以解除这道题目了。——如果我们要用求深度的办法,那么需要先求出每个叶子结点的深度,然后再比较他们之间的大小取出最大深度。
二叉树高度——》左右中——后续遍历
整体c++代码如下:
class solution { public: //1.确定递归函数的参数和返回值:参数就是传入树的根节点,返回就返回这棵树的深度,所以返回值为int类型 int getdepth(TreeNode* node) { //2.确定终止条件:如果为空节点的话,就返回0,表示高度为0 if (node == NULL) return 0; //3.确定单层递归的逻辑 int leftdepth = getdepth(node->left); // 左 int rightdepth = getdepth(node->right); // 右 int depth = 1 + max(leftdepth, rightdepth); // 中 return depth; } int maxDepth(TreeNode* root) { return getdepth(root); } };
代码精简之后c++代码如下:
class solution { public: int maxDepth(TreeNode* root) { if (root == null) return 0; return 1 + max(maxDepth(root->left), maxDepth(root->right)); } };
精简之后的代码根本看不出是哪种遍历方式,也看不出递归三部曲的步骤,所以如果对二叉树的操作还不熟练,尽量不要直接照着精简代码来学。
(2)前序遍历--求深度
本题当然也可以使用前序,代码如下:(充分表现出求深度回溯的过程)
class solution { public: int result; void getdepth(TreeNode* node, int depth) { result = depth > result ? depth : result; // 中 if (node->left == NULL && node->right == NULL) return ; if (node->left) { // 往左走 depth++; // 只要往左走,就深度+1 getdepth(node->left, depth); depth--; // 回溯,深度-1——》未来不干扰getdepth(node->right, depth)中depth的计算(难点) } if (node->right) { // 右 depth++; // 深度+1 getdepth(node->right, depth); depth--; // 回溯,深度-1 } return ; } int maxDepth(TreeNode* root) { result = 0;//存储最大深度 if (root == NULL) return result; getdepth(root, 1);//初始化根节点的depth为1 return result; } };
可以看出使用了前序(中左右)的遍历顺序,这才是真正求深度的逻辑!
注意以上代码是为了把细节体现出来,简化一下代码如下:
class solution { public: int result; void getdepth(TreeNode* node, int depth) { result = depth > result ? depth : result; // 中 if (node->left == NULL && node->right == NULL) return ; if (node->left) { // 左 getdepth(node->left, depth + 1);//传入depth + 1并不会改变当层的depth的值 } if (node->right) { // 右 getdepth(node->right, depth + 1); } return ; } int maxDepth(TreeNode* root) { result = 0; if (root == 0) return result; getdepth(root, 1); return result; } };
2.迭代法
使用迭代法的话,使用层序遍历是最为合适的,因为最大的深度就是二叉树的层数,和层序遍历的方式极其吻合。
在二叉树中,一层一层的来遍历二叉树,记录一下遍历的层数就是二叉树的深度,如图所示:
所以这道题的迭代法就是一道模板题,可以使用二叉树层序遍历的模板来解决的。
如果对层序遍历还不清楚的话,可以看这篇:二叉树:层序遍历登场!(opens new window)
c++代码如下:
class solution { public: int maxDepth(TreeNode* root) { if (root == NULL) return 0; int depth = 0; queue<TreeNode*> que; que.push(root); while(!que.empty()) { int size = que.size(); depth++; // 记录深度 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); } } return depth; } };
那么我们可以顺便解决一下n叉树的最大深度问题
559.n叉树的最大深度
给定一个 n 叉树,找到其最大深度。
最大深度是指从根节点到最远叶子节点的最长路径上的节点总数。
例如,给定一个 3叉树 :
我们应返回其最大深度,3。
思路:
依然可以提供递归法和迭代法,来解决这个问题,思路是和二叉树思路一样的,直接给出代码如下:
1.递归法
c++代码:
/* // 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 == 0) return 0;//递归出口 int depth = 0; for (int i = 0; i < root->children.size(); i++) {//n叉树和二叉树唯一不同的地方就是遍历子节点的方式不同 depth = max (depth, maxDepth(root->children[i])); } return depth + 1; } };
2.迭代法(一刷没看)
依然是层序遍历,代码如下:
class solution { public: int maxDepth(Node* root) { queue<Node*> que; if (root != NULL) que.push(root); int depth = 0; while (!que.empty()) { int size = que.size(); depth++; // 记录深度 for (int i = 0; i < size; i++) { Node* node = que.front(); que.pop(); for (int j = 0; j < node->children.size(); j++) { if (node->children[j]) que.push(node->children[j]); } } } return depth; } };
111.二叉树的最小深度
tip:一定要把二叉树求最大深度和求最小深度的代码进行对比!!
给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量——》所以如果根节点的左节点是null,那么这个二叉树的最小深度不是1,而需要找根节点的右子树中的叶子节点
说明: 叶子节点是指没有子节点的节点。
示例:
给定二叉树 [3,9,20,null,null,15,7],
返回它的最小深度 2
思路
本题依然是前序遍历和后序遍历都可以,前序(中左右)求的是深度,后序(左右中)求的是高度。——因为用后序遍历比用前序遍历的代码更简洁,所以网上大多用的是后序遍历。
-
二叉树节点的深度:指从根节点到该节点的最长简单路径边的条数或者节点数(取决于深度从0开始还是从1开始)
-
二叉树节点的高度:指从该节点到叶子节点的最长简单路径边的条数后者节点数(取决于高度从0开始还是从1开始)
那么使用后序遍历,其实求的是根节点到叶子节点的最小距离,就是求高度的过程,不过这个最小距离 也同样是最小深度。
以下讲解中遍历顺序上依然采用后序遍历(因为要比较递归返回之后的结果,本文我也给出前序遍历的写法)。
本题还有一个误区,在处理节点的过程中,最大深度很容易理解,最小深度就不那么好理解,如图:
这就重新审题了,题目中说的是:最小深度是从根节点到最近叶子节点的最短路径上的节点数量。,注意是叶子节点。
什么是叶子节点,左右孩子都为空的节点才是叶子节点!
1.递归法
(1)后序遍历
递归三部曲:
1.确定递归函数的参数和返回值
参数为要传入的二叉树根节点,返回的是int类型的深度。
代码如下:
int getDepth(TreeNode* node)
2.确定终止条件
终止条件也是遇到空节点返回0,表示当前节点的高度为0。
代码如下:
if (node == NULL) return 0;
3.确定单层递归的逻辑
这块和求最大深度可就不一样了,一些同学可能会写如下代码:
int leftDepth = getDepth(node->left); int rightDepth = getDepth(node->right); int result = 1 + min(leftDepth, rightDepth);//这里就犯错了!!!用min函数是有条件的!! return result;
如果像上面的代码那么求的话,没有左孩子的分支会算为最短深度。
所以,如果左子树为空,右子树不为空,说明最小深度是 1 + 右子树的深度。
反之,右子树为空,左子树不为空,最小深度是 1 + 左子树的深度。 最后如果左右子树都不为空,返回左右子树深度最小值 + 1 。
代码如下:
int leftDepth = getDepth(node->left); // 左 int rightDepth = getDepth(node->right); // 右 // 中 // 当一个左子树为空,右不为空,这时并不是最低点 if (node->left == NULL && node->right != NULL) { return 1 + rightDepth; } // 当一个右子树为空,左不为空,这时并不是最低点 if (node->left != NULL && node->right == NULL) { return 1 + leftDepth; } //左子树和右子树都不为空的情况才能使用min函数 int result = 1 + min(leftDepth, rightDepth); return result;
遍历的顺序为后序(左右中),可以看出:求二叉树的最小深度和求二叉树的最大深度的差别主要在于处理左右孩子不为空的逻辑。
整体递归代码如下:
class Solution { public: int getDepth(TreeNode* node) { if (node == NULL) return 0; int leftDepth = getDepth(node->left); // 左 int rightDepth = getDepth(node->right); // 右 // 中 // 当一个左子树为空,右不为空,这时并不是最低点 if (node->left == NULL && node->right != NULL) { return 1 + rightDepth; } // 当一个右子树为空,左不为空,这时并不是最低点 if (node->left != NULL && node->right == NULL) { return 1 + leftDepth; } int result = 1 + min(leftDepth, rightDepth); return result; } int minDepth(TreeNode* root) { return getDepth(root); } };
精简之后代码如下:
class Solution { public: int minDepth(TreeNode* root) { if (root == NULL) return 0; if (root->left == NULL && root->right != NULL) { return 1 + minDepth(root->right); } if (root->left != NULL && root->right == NULL) { return 1 + minDepth(root->left); } return 1 + min(minDepth(root->left), minDepth(root->right)); } };
(2)前序遍历
一刷没看
2.迭代法
一刷没看
222.完全二叉树的节点个数
给出一个完全二叉树,求出该树的节点个数。
示例 1:
-
输入:root = [1,2,3,4,5,6]
-
输出:6
示例 2:
-
输入:root = []
-
输出:0
示例 3:
-
输入:root = [1]
-
输出:1
提示:
-
树中节点的数目范围是[0, 5 * 10^4]
-
0 <= Node.val <= 5 * 10^4
-
题目数据保证输入的树是 完全二叉树
这道题强调完全二叉树,就是在提醒我们尽量使用完全二叉树的特性。如果是普通的二叉树,其实我们使用任何一种遍历方式都可以统计出二叉树的节点数量。
1.普通二叉树-后续遍历(铺垫)
首先按照普通二叉树的逻辑来求。
这道题目的递归法和求二叉树的深度写法类似, 而迭代法,二叉树:层序遍历登场! (opens new window)遍历模板稍稍修改一下,记录遍历的节点数量就可以了。
递归遍历的顺序依然是后序(左右中)。
(1)递归
如果对求二叉树深度还不熟悉的话,看这篇:二叉树:看看这些树的最大深度 (opens new window)。
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;
所以整体C++代码如下:就是经典的后序遍历模板,只不过需要加一个统计的操作
// 版本一 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); } };
代码精简之后C++代码如下:
// 版本二 class Solution { public: int countNodes(TreeNode* root) { if (root == NULL) return 0; return 1 + countNodes(root->left) + countNodes(root->right); } };
-
时间复杂度:O(n)
-
空间复杂度:O(log n),算上了递归系统栈占用的空间
网上基本都是这个精简的代码版本,其实不建议大家照着这个来写,代码确实精简,但隐藏了一些内容,连遍历的顺序都看不出来,所以初学者建议学习版本一的代码,稳稳的打基础。
(2)迭代法
那么只要模板少做改动,加一个变量result,统计节点数量就可以了
class Solution { public: int countNodes(TreeNode* root) { queue<TreeNode*> que; if (root != NULL) que.push(root); int result = 0; while (!que.empty()) { int size = que.size(); for (int i = 0; i < size; i++) { TreeNode* node = que.front(); que.pop(); result++; // 记录节点数量 if (node->left) que.push(node->left); if (node->right) que.push(node->right); } } return result; } };
-
时间复杂度:O(n)
-
空间复杂度:O(n)
2.完全二叉树的思路
完全二叉树,从上到下是满的;底层结点必须从左到右是连续的,不能断开:
完全二叉树只有两种情况,情况一:就是满二叉树,情况二:最后一层叶子节点没有满。
-
对于情况一,可以直接用 2^树深度 - 1 来计算,注意这里根节点深度为1。
-
对于情况二,分别递归左孩子,和右孩子,递归到某一深度一定会有左孩子或者右孩子为满二叉树,然后依然可以按照情况1来计算。
完全二叉树(一)如图:
完全二叉树(二)如图:
在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2^(h-1) 个节点。
满二叉树(假设层数为n),则结点总数为(2^n-1)
;所以可以判断左右子树是不是满二叉树,是满二叉树的话就直接用(2^n-1)
这个公式去计算。如果某个子树不是满二叉树,就在继续判断以这个子树为根节点的左右子树是不是满二叉树,如果是则直接使用计算公式!以此类推...
判断是不是满二叉树的办法:判断一个根节点“一直向左遍历的最大深度”和“一直向右遍历的最大深度”是否相同。(这样相比于普通二叉树来说,遍历的数量变少了,因为普特二叉树需要遍历树中所有结点;而完全二叉树只需要遍历每个根节点的最左侧一列和最后侧一列)
在完全二叉树中,如果递归向左遍历的深度等于递归向右遍历的深度,那说明就是满二叉树。如图:
在完全二叉树中,如果递归向左遍历的深度不等于递归向右遍历的深度,则说明不是满二叉树,如图:
那有录友说了,这种情况,递归向左遍历的深度等于递归向右遍历的深度,但也不是满二叉树,如题:
如果这么想,大家就是对 完全二叉树理解有误区了,以上这棵二叉树,它根本就不是一个完全二叉树!
判断其子树是不是满二叉树,如果是则利用公式计算这个子树(满二叉树)的节点数量,如果不是则继续递归,那么 在递归三部曲中,第二部:终止条件的写法应该是这样的:
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,返回满足满二叉树的子树节点数量 }
递归三部曲,第三部,单层递归的逻辑:(可以看出使用后序遍历)
int leftTreeNum = countNodes(root->left); // 左 int rightTreeNum = countNodes(root->right); // 右 int result = leftTreeNum + rightTreeNum + 1; // 中 return result;
该部分精简之后代码为:
return countNodes(root->left) + countNodes(root->right) + 1;
最后整体C++代码如下:
class Solution { public: int countNodes(TreeNode* root) { if (root == nullptr) return 0;//递归出口1 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) {//递归出口2 return (2 << leftDepth) - 1; // 注意(2<<1) 相当于2^2,所以leftDepth初始为0;leftDepth=0时,(2 << leftDepth) - 1=1 } return countNodes(root->left) + countNodes(root->right) + 1; } };
-
时间复杂度:O(log n × log n)
-
空间复杂度:O(log n)