目录
代码随想录day16
104.二叉树的最大深度
思路:
559、N叉树的最大深度
思路:
111.二叉树的最小深度
思路:
222、完全二叉树的节点数
思路:
给定一个二叉树,找出其最大深度。二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明: 叶子节点是指没有子节点的节点。
示例: 给定二叉树 [3,9,20,null,null,15,7],
返回它的最大深度 3 。
可以用层序遍历模板,每遍历一层就计数+1。代码可以参考我前面发的层序遍历10道题目中有这道题。
递归法:
本题可以使用前序(中左右),也可以使用后序遍历(左右中),使用前序求的就是深度,使用后序求的是高度。
而根节点的高度就是二叉树的最大深度,所以本题中我们通过后序求的根节点高度来求的二叉树最大深度。
这一点其实是很多同学没有想清楚的,很多题解同样没有讲清楚。
我先用后序遍历(左右中)来计算树的高度。
1、确定递归函数的参数和返回值:参数就是传入树的根节点,返回就返回这棵树的深度,所以返回值为int类型。
2、确定终止条件:如果为空节点的话,就返回0,表示高度为0。
3、确定单层递归的逻辑:先求它的左子树的深度,再求的右子树的深度,最后取左右深度最大的数值 再+1 (加1是因为算上当前中间节点)就是目前节点为根节点的树的深度。
func maxDepth(root *TreeNode) int {
return getdepth(root)
}
func getdepth(node *TreeNode) int { //1.参数和返回值
if node == nil { // 2. 递归终止条件
return 0
}
// 3. 单层递归逻辑,后序遍历
l_depth := getdepth(node.Left) // 左
r_depth := getdepth(node.Right) // 右
depth := 1 + max(l_depth, r_depth) // 中
return depth
}
func max(a , b int) int {
if a > b {
return a
}
return b
}
当然啦以上代码可以简写为以下:
func maxDepth(root *TreeNode) int {
if root == nil {
return 0
}
return 1 + max(maxDepth(root.Left), maxDepth(root.Right))
}
func max(a , b int) int {
if a > b {
return a
}
return b
}
精简之后的代码根本看不出是哪种遍历方式,也看不出递归三部曲的步骤,所以如果对二叉树的操作还不熟练,尽量不要直接照着精简代码来学。
给定一个 n 叉树,找到其最大深度。最大深度是指从根节点到最远叶子节点的最长路径上的节点总数。
例如,给定一个 3叉树 :
我们应返回其最大深度,3。
依然可以提供递归法和迭代法(层序遍历),来解决这个问题,思路是和二叉树思路一样的。层序遍历的代码可以参照前面的层序遍历10道题里面有。递归法直接给出代码如下:
func maxDepth(root *Node) int {
if root == nil {
return 0
}
depth := 0
for i:=0;i b {
return a
}
return b
}
给定一个二叉树,找出其最小深度。最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
说明: 叶子节点是指没有子节点的节点。
示例:
给定二叉树 [3,9,20,null,null,15,7], 返回它的最小深度 2.
看完了上面求最大深度的题目,再来看看如何求最小深度。直觉上好像和求最大深度差不多,其实还是差不少的。本题依然是前序遍历和后序遍历都可以,前序求的是深度,后序求的是高度。
那么使用后序遍历,其实求的是根节点到叶子节点的最小距离,就是求高度的过程,这不过这个最小距离 也同样是最小深度。
以下讲解中遍历顺序上依然采用后序遍历(因为要比较递归返回之后的结果)。
本题还有一个误区,在处理节点的过程中,最大深度很容易理解,最小深度就不那么好理解,如图:
这就重新审题了,题目中说的是:最小深度是从根节点到最近叶子节点的最短路径上的节点数量。,注意是叶子节点。
什么是叶子节点,左右孩子都为空的节点才是叶子节点!
递归三部曲:
1、确定递归函数的参数和返回值:
参数为要传入的二叉树根节点,返回的是int类型的深度
2、确定终止条件:
终止条件也是遇到空节点返回0,表示当前节点的高度为0。
3、确定单层递归的逻辑
这块和求最大深度可就不一样了,一些同学可能会写如下代码:
leftDepth := getDepth(node.left)
rightDepth := getDepth(node.right)
result = 1 + min(leftDepth, rightDepth)
return result
这个代码就犯了此图中的误区:
如果这么求的话,没有左孩子的分支会算为最短深度。
代码如下:
leftDepth = getDepth(node.left); // 左
rightDepth = getDepth(node.right); // 右
// 中
// 当一个左子树为空,右不为空,这时并不是最低点
if node.left == nil && node.right != nil {
return 1 + rightDepth
}
// 当一个右子树为空,左不为空,这时并不是最低点
if node.left != nil && node.right == nil {
return 1 + leftDepth
}
result = 1 + min(leftDepth, rightDepth)
return result
遍历的顺序为后序(左右中),可以看出:求二叉树的最小深度和求二叉树的最大深度的差别主要在于处理左右孩子不为空的逻辑。
整体递归代码如下:
func minDepth(root *TreeNode) int {
return getDepth(root)
}
func getDepth(node *TreeNode) int {
if node == nil {
return 0
}
l_depth := getDepth(node.Left)
r_depth := getDepth(node.Right)
if node.Left == nil && node.Right != nil {
return 1 + r_depth
}
if node.Right == nil && node.Left != nil {
return 1 + l_depth
}
result := 1 + min(l_depth, r_depth)
return result
}
func min(a, b int) int {
if a > b {
return b
}
return a
}
精简之后代码如下:
func minDepth(root *TreeNode) int {
if root == nil {
return 0
}
if root.Left == nil && root.Right != nil {
return 1 + minDepth(root.Right)
}
if root.Right == nil && root.Left != nil {
return 1 + minDepth(root.Left)
}
return 1 + min(minDepth(root.Left), minDepth(root.Right))
}
func min(a, b int) int {
if a > b {
return b
}
return a
}
精简之后的代码根本看不出是哪种遍历方式,所以依然还要强调一波:如果对二叉树的操作还不熟练,尽量不要直接照着精简代码来学。
给出一个完全二叉树,求出该树的节点个数。
示例 1:
- 输入:root = [1,2,3,4,5,6]
- 输出:6
示例 2:
- 输入:root = []
- 输出:0
示例 3:
- 输入:root = [1]
- 输出:1
本篇给出按照普通二叉树的求法以及利用完全二叉树性质的求法。
这道题目的递归法和求二叉树的深度写法类似, 而迭代法用层序遍历模板稍稍修改一下,记录遍历的节点数量就可以了。递归遍历的顺序依然是后序(左右中)。
递归法:
1、确定递归函数的参数和返回值:参数就是传入树的根节点,返回就返回以该节点为根节点二叉树的节点数量,所以返回值为int类型。
2、确定终止条件:如果为空节点的话,就返回0,表示节点数为0。
3、确定单层递归的逻辑:先求它的左子树的节点数量,再求的右子树的节点数量,最后取总和再加一 (加1是因为算上当前中间节点)就是目前节点为根节点的节点数量。
所以整体Go代码如下:
func countNodes(root *TreeNode) int {
return getNodesNum(root)
}
func getNodesNum(node *TreeNode) int { // 1.确定递归参数以及返回值类型
// 2,确定递归终止条件
if node == nil {
return 0
}
// 3.确定单层递归逻辑
l_Num := getNodesNum(node.Left)
r_Num := getNodesNum(node.Right)
Tree_Num := 1 + l_Num + r_Num
return Tree_Num
}
代码精简之后Go代码如下:
func countNodes(root *TreeNode) int {
if root == nil {
return 0
}
return 1 + countNodes(root.Left) + countNodes(root.Right)
}
网上基本都是这个精简的代码版本,其实不建议大家照着这个来写,代码确实精简,但隐藏了一些内容,连遍历的顺序都看不出来,所以初学者建议学习版本一的代码,稳稳的打基础。
用迭代法就是每层的元素无脑计数就可以了。
func countNodes(root *TreeNode) int {
ans := 0
if root == nil {
return 0
}
queue := list.New()
queue.PushBack(root)
for queue.Len() > 0 {
length := queue.Len()
for i:=0;i
以上方法都是按照普通二叉树来做的,对于完全二叉树特性不了解的同学可以看这篇 关于二叉树,你该了解这些!这篇详细介绍了各种二叉树的特性。
在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2^(h-1) 个节点。
大家要自己看完全二叉树的定义,很多同学对完全二叉树其实不是真正的懂了。
我来举一个典型的例子如题:
完全二叉树只有两种情况,情况一:就是满二叉树,情况二:最后一层叶子节点没有满。
完全二叉树(一)如图:
完全二叉树(二)如图:
可以看出如果整个树不是满二叉树,就递归其左右孩子,直到遇到满二叉树为止,用公式计算这个子树(满二叉树)的节点数量。
这里关键在于如果去判断一个左子树或者右子树是不是满二叉树呢?
在完全二叉树中,如果递归向左遍历的深度等于递归向右遍历的深度,那说明就是满二叉树。如图:
在完全二叉树中,如果递归向左遍历的深度不等于递归向右遍历的深度,则说明不是满二叉树,如图:
那有录友说了,这种情况,递归向左遍历的深度等于递归向右遍历的深度,但也不是满二叉树,如题:
如果这么想,大家就是对 完全二叉树理解有误区了,以上这棵二叉树,它根本就不是一个完全二叉树!
判断其子树岂不是满二叉树,如果是则利用公式计算这个子树(满二叉树)的节点数量,如果不是则继续递归,那么 在递归三部曲中,第二部:终止条件的写法应该是这样的:
if root == nil {
return 0
}
// 开始根据左深度和右深度是否相同来判断该子树是不是满二叉树
left := root.Left
right := root.Right
l_dpeth, r_depth := 0, 0 // 这里初始化为0是有目的的,为了下面求指数方便
for left != nil { // 求左子树高度
left = left.Left
l_depth++
}
for right != nil { // 求右子树高度
right = right.Right
r_depth++
}
if l_depth == r_depth {
return (2 << l_depth) - 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;
最后整体Go代码如下:
func countNodes(root *TreeNode) int {
if root == nil {
return 0
}
l_depth, r_depth := 0, 0
left := root.Left
right := root.Right
for left != nil {
left = left.Left
l_depth++
}
for right != nil {
right = right.Right
r_depth++
}
if l_depth == r_depth {
return (2 << l_depth) - 1
}
return countNodes(root.Left) + countNodes(root.Right) + 1
}
就这些。