其实很简单,树的解法通常都是递归,编写递归算法只可能有两种思维模式,都尝试套用一下,必然有一种能写出来:
一种是「遍历」的思维模式,另一种是「分解问题」的思维模式。
- 遍历的思维模式:如果你想用「遍历」的思维模式来写递归算法,那么你需要一个无返回值的遍历函数,在遍历的过程中收集结果。
- 分解问题的思维模式:如果你想用「分解问题」的思维模式来写递归算法,那么这个递归函数一定要有一个清晰的定义,说明这个函数参数的含义是什么,返回什么结果,也就是一个带返回值的函数。这样你才能利用这个定义来计算子问题,反推原问题的解。
解法一:分解问题思维模式的递归法
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/
func maxDepth(root *TreeNode) int {
if root == nil{ // 递归退出条件,遍历到空节点,无深度
return 0
}
// 左右子树中深度较大值,再加上当前节点这一层,就是答案
leftMaxDepth := maxDepth(root.Left)
rightMaxDepth := maxDepth(root.Right)
return max(leftMaxDepth, rightMaxDepth) + 1
}
func max(a, b int) int{
if a > b {
return a
}
return b
}
解法二:遍历思维模式的递归法
用标准的二叉树遍历函数 traverse 来遍历整棵树,在遍历的过程更新最大深度,这样当遍历完所有节点时,必然可以求出整棵树的最大深度。
traverse函数框架,所谓的前中后序遍历,其实就是在二叉树遍历框架的不同位置写代码
- 前序位置的代码会在进入节点时立即执行;
- 中序位置的代码会在左子树遍历完成后,遍历右子树之前执行;
- 后序位置的代码会在左右子树遍历完成后执行
// 基本的二叉树节点
type TreeNode struct {
Val int
Left *TreeNode
Right *TreeNode
}
// 二叉树的递归遍历框架
func traverse(root *TreeNode) {
if root == nil {
return
}
// 前序位置逻辑...
traverse(root.Left)
// 中序位置逻辑...
traverse(root.Right)
// 后序位置逻辑...
}
那么本地可以在遍历这棵树的过程中,不断更新一个depth变量,在遍历过程中更新最大深度
我们可以定义一个无返回值的traverse函数
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/
func maxDepth(root *TreeNode) int {
var depth, res int
traverse(root, &depth, &res)
return res
}
func traverse(root *TreeNode, depth *int, res *int) {
if root == nil{ // 递归退出条件,遍历到空节点
return
}
// 前序遍历位置,进入节点时增加深度
*depth++
// 遍历到叶子节点时,更新答案
if root.Left == nil && root.Right == nil{
*res = max(*res, *depth)
}
traverse(root.Left, depth, res) // 继续遍历左右子树
traverse(root.Right, depth, res)
// 后序遍历位置,离开节点时回溯,减少深度
*depth--
}
func max(a, b int) int{
if a > b {
return a
}
return b
}