首页 > 编程语言 >算法练习-第十六天【二叉树】

算法练习-第十六天【二叉树】

时间:2022-10-12 01:12:46浏览次数:76  
标签:第十六 return nil int 算法 二叉树 TreeNode root

二叉树的深度与高度

二叉树的深度:从根节点到该节点的最长简单路径边的条数或节点数(取决于深度是否从1开始)
二叉树的高度:从该节点到叶子节点的最长简单路径边的条数或节点数(取决于高度是否从1开始)

104.二叉树的最大深度

参考:代码随想录

思考

根据二叉树深度和高度的定义可知根节点的高度就是二叉树的最大深度
二叉树的前序遍历:顺序是中左右,求二叉树的深度;
二叉树的后序遍历:顺序是左右中,求二叉树的高度;

递归

  1. 确定递归函数参数与返回值:int maxDepth(*TreeNode root)
  2. 递归的终止条件: 节点为空
  3. 单层递归处理逻辑:先求左子树深度,在求右子树深度,最后左右子树深度最大值。
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
}

前序遍历的方法

func maxDepth(root *TreeNode) int {
	if root == nil {
		return 0
	}
	rlt := 0
	var getDepth func(*TreeNode, int)
	getDepth = func(node *TreeNode, depth int) {
		// 前序遍历
		if rlt < depth {
			rlt = depth
		}

		if node.Left != nil {
			getDepth(node.Left, depth+1)
		}

		if node.Right != nil {
			getDepth(node.Right, depth+1)
		}
	}
	getDepth(root, 1)

	return rlt
}

迭代法

使用层序遍历求最大深度更容易理解,只需要在处理每层节点之前进行层数计数。

func maxDepth(root *TreeNode) int {
	if root == nil {
		return 0
	}
	depth := 0
	queue := list.New()
	queue.PushBack(root)
	for queue.Len() > 0 {
		length := queue.Len()
		depth++ // 层数+1
		for ; length > 0; length-- {
			cur := queue.Remove(queue.Front()).(*TreeNode)
			if cur.Left != nil {
				queue.PushBack(cur.Left)
			}
			if cur.Right != nil {
				queue.PushBack(cur.Right)
			}
		}
	}

	return depth
}

总结

求解二叉树的最大深度可以使用递归和迭代法, 在递归法中要明白是前序遍历还是后序遍历。

559. N 叉树的最大深度

思考

求N叉树的最大深度与二叉树的最大深度思路是一样的,下面直接贴出相关代码

递归

/**
 * Definition for a Node.
 * type Node struct {
 *     Val int
 *     Children []*Node
 * }
 */
func maxDepth(root *Node) int {
	if root == nil {
		return 0
	}
	depth := 0
	for i := 0; i < len(root.Children); i++ {
		depth = max(maxDepth(root.Children[i]), depth)
	}

	return 1 + depth
}

func max(a, b int) int {
	if a > b {
		return a
	}

	return b
}

迭代法

使用层序遍历

/**
 * Definition for a Node.
 * type Node struct {
 *     Val int
 *     Children []*Node
 * }
 */
func maxDepth(root *Node) int {
	if root == nil {
		return 0
	}

	queue := list.New()
	queue.PushBack(root)
	depth := 0
	for queue.Len() > 0 {
		length := queue.Len()
		depth++
		for i := 0; i < length; i++ {
			cur := queue.Remove(queue.Front()).(*Node)
			for j := 0; j < len(cur.Children); j++ {
				if cur.Children[j] != nil {
					queue.PushBack(cur.Children[j])
				}
			}
		}
	}

	return depth
}

111. 二叉树的最小深度

参考:代码随想录

思考

求二叉树的最小深度就是求解根节点到叶子节点的距离,与二叉树的最大深度类似,需要注意的是:最小深度是从根节点到最近叶子节点的最短路径上的节点数量,因此我们需要遍历到叶子节点。

递归

  1. 确定递归函数的参数与返回值: minDepth(root *TreeNode)
  2. 递归的终止条件: if root == nil; return 0
  3. 单层递归逻辑:
    leftDepth := minDepth(root.Left)   // 左
    rightDepth := minDepth(root.Right) // 右
    
    // 中
    if root.Left == nil && root.Right != nil {
    	return 1 + rightDepth
    }
    if root.Left != nil && root.Right == nil {
    	return 1 + leftDepth
    }
    

完整代码如下:

/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
func minDepth(root *TreeNode) int {
	// 后序遍历递归
	if root == nil {
		return 0
	}
	leftDepth := minDepth(root.Left)   // 左
	rightDepth := minDepth(root.Right) // 右

	// 中
	if root.Left == nil && root.Right != nil {
		return 1 + rightDepth
	}
	if root.Left != nil && root.Right == nil {
		return 1 + leftDepth
	}

	return 1 + min(leftDepth, rightDepth)
}

func min(a, b int) int {
	if a < b {
		return a
	}

	return b
}

前序遍历

func minDepth(root *TreeNode) int {
	if root == nil {
		return 0
	}

	rlt := math.MaxInt32
	var getdepth func(*TreeNode, int)
	getdepth = func(node *TreeNode, depth int) {
		// 递归结束条件
		if node.Left == nil && node.Right == nil {
			if rlt > depth {
				rlt = depth
			}
			return
		}
		if node.Left != nil {
			getdepth(node.Left, depth+1)
		}
		if node.Right != nil {
			getdepth(node.Right, depth+1)
		}
	}
	getdepth(root, 1)

	return rlt
}

迭代法

求解最小的深度使用层序遍历依然适用, 从根节点一层一层向下遍历,当遇到左右孩子都为空时,就返回,此时的深度最小。

/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
func minDepth(root *TreeNode) int {
	if root == nil {
		return 0
	}
	queue := list.New()
	queue.PushBack(root)
	depth := 0
	for queue.Len() > 0 {
		length := queue.Len()
		depth++
		for ; length > 0; length-- {
			cur := queue.Remove(queue.Front()).(*TreeNode)
			if cur.Left == nil && cur.Right == nil {
				return depth
			}
			if cur.Left != nil {
				queue.PushBack(cur.Left)
			}

			if cur.Right != nil {
				queue.PushBack(cur.Right)
			}
		}
	}

	return depth
}

总结

求二叉树的最小深度与最大深度的不同在于处理左右孩子为空的逻辑。

222. 完全二叉树的节点个数

参考:代码随想录
完全二叉树的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2h 个节点。

思考

求二叉树的节点数量,遍历完一整颗树即可得到。先求左子树节点数量再求右子树节点,最后将左右节点数相加。
使用后序遍历示例代码:

/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
func countNodes(root *TreeNode) int {
	if root == nil {
		return 0
	}
	leftCount := countNodes(root.Left)
	rightCount := countNodes(root.Right)

	return 1 + leftCount + rightCount
}

进阶

根据完全二叉树的性质可知,一个完全二叉树有两种情况:

  1. 满二叉树: 满二叉树计算节点数:\(2^n-1\),根节点的深度为1
  2. 叶子节点没有满: 分别递归左孩子和右孩子,递归到一定深度一定会有左孩子或者右孩子是一颗满二叉树(单个节点也满足一颗满二叉树)

如何判断满二叉树:
递归向左遍历的深度等于递归向右遍历的深度时,说明是一颗满二叉树。

  1. 确定递归函数参数与返回值:func countNodes(root *TreeNode) int,返回满二叉树的节点数。

  2. 递归的终止条件为:

    if root == nil {
    		return 0
    	}
    	leftH, rightH := 0, 0
    	leftNode, rightNode := root.Left, root.Right
    	for leftNode != nil {
    		leftH++
    		leftNode = leftNode.Left
    	}
    	for rightNode != nil {
    		rightH++
    		rightNode = rightNode.Right
    	}
    
    	if leftH == rightH {
    		return 2<<leftH - 1
    	}
    
  3. 单层递归逻辑:分别计算左子树与右子树的节点数,然后相加。
    整体代码如下:


/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
func countNodes(root *TreeNode) int {
	//完全二叉树的特性
	if root == nil {
		return 0
	}
	leftH, rightH := 0, 0
	leftNode, rightNode := root.Left, root.Right
	for leftNode != nil {
		leftH++
		leftNode = leftNode.Left
	}
	for rightNode != nil {
		rightH++
		rightNode = rightNode.Right
	}

	if leftH == rightH {
		return 2<<leftH - 1
	}

	return 1 + countNodes(root.Left) + countNodes(root.Right)
}

总结

利用完全二叉树的特性可以判断是否是满二叉树,根据满二叉树的节点计算公式\(2^n-1\)计算总的节点数。
时间复杂度: \(O(logn*logn)\)
空间复杂度: \(O(logn)\)

标签:第十六,return,nil,int,算法,二叉树,TreeNode,root
From: https://www.cnblogs.com/neilliu/p/16783041.html

相关文章

  • LeetCode算法笔记 566. 重塑矩阵
    importjunit.framework.TestCase;importjava.util.Arrays;publicclassLeetCode04_1extendsTestCase{/***566.重塑矩阵*在MATLAB中,有......
  • LeetCode算法笔记 121. 买卖股票的最佳时机
    importjunit.framework.TestCase;publicclassLeetCode03_2extendsTestCase{/***121.买卖股票的最佳时机*给定一个数组prices,它的第i......
  • 视频+课件|单目6D姿态估计算法详解
    写在前面感谢「3D视觉从入门到精通」知识星球嘉宾王谷博士为我们带来的主题为单目6D物体姿态估计算法视频讲解,星球成员可免费观看学习。备注:王谷博士,清华大学自动化系BBNCL......
  • 大厂HR:我们根本招不到合格的算法工程师
    在人工智能机器学习的领域中,目前最火的莫过于计算机视觉了,这项技术一直广受关注,而其中的目标检测是计算机视觉领域中最常见的问题之一。从去年的YOLOv4发布后,目标检测框架......
  • 算法的时间复杂度、空间复杂度
    前言本文主要记录了数据结构、算法、数据结构与算法的关系以及算法的时间复杂度、空间复杂度。数据结构数据结构是计算机存储、组织数据的方式。算法算法是一系列解决......
  • 算法竞赛入门【码蹄集新手村600题】(MT1451-1500)
    算法竞赛入门【码蹄集新手村600题】(MT1451-1500)文章目录​​算法竞赛入门【码蹄集新手村600题】(MT1451-1500)​​​​前言​​​​为什么突然想学算法了?​​​​为什么选择......
  • 算法竞赛入门【暑期速成计划】(一)
    算法竞赛入门【暑期速成计划】(一)文章目录​​算法竞赛入门【暑期速成计划】(一)​​​​前言​​​​为什么突然想学算法了?​​​​一、程序设计入门​​​​(一)、变量及其输入......
  • 算法竞赛入门【码蹄集新手村600题】(MT1101-1150)
    算法竞赛入门【码蹄集新手村600题】(MT1101-1150)文章目录​​算法竞赛入门【码蹄集新手村600题】(MT1101-1150)​​​​前言​​​​为什么突然想学算法了?​​​​为什么选择码......
  • 算法竞赛入门【码蹄集新手村600题】(MT1201-1250)
    算法竞赛入门【码蹄集新手村600题】(MT1201-1250)文章目录​​算法竞赛入门【码蹄集新手村600题】(MT1201-1250)​​​​前言​​​​为什么突然想学算法了?​​​​为什么选择码......
  • 算法竞赛入门【码蹄集新手村600题】(MT1251-1300)
    算法竞赛入门【码蹄集新手村600题】(MT1251-1300)文章目录​​算法竞赛入门【码蹄集新手村600题】(MT1251-1300)​​​​前言​​​​为什么突然想学算法了?​​​​为什么选择......