二叉树专题

·  阅读 33

1. 二叉树的定义

树的每个节点最多有两个子节点
二叉树的定义如下:

type TreeNode struct {
	Val   int
	Left  *TreeNode
	Right *TreeNode
}
复制代码

2. 基本操作

树的核心就是遍历,无论什么操作(比如按条件检索,修改等),都是建立在遍历的基础上
树的遍历有两种思想:

  • 一条路走到底的深度优先搜素(DFS),包括:
    • 先序遍历(根左右)
    • 中序遍历(左根右)
    • 前序遍历(左右根)
  • 先把相邻所有节点都走一遍的广度优先搜索(BFS),包括:
    • 层序遍历

1. 先序、中序、后序递归

先序、中序、后序,在遍历过程中经过节点的路线是一样的,只是访问节点时机不同,即:

  • 先序遍历是第一次"遇到"该结点时访问
  • 中序遍历是第二次"遇到"该结点(此时该结点从左子树返回)时访问
  • 后序遍历是第三次"遇到"该结点(此时该结点从右子树返回)时访问


其模板如下:

func reverse( root *TreeNode ){
    // 主逻辑在这,先序
    reverse(root.Left)
    // 主逻辑在这,中序
    reverse(root.Right)
    // 主逻辑在这,后序
}
复制代码

94. 二叉树的中序遍历

/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
var res []int
func inorderTraversal(root *TreeNode) []int {
    res = make([]int, 0)
    dfs(root)
    return res
}

func dfs(root *TreeNode) {
    if root == nil {
        return
    }
    dfs(root.Left)
    res = append(res, root.Val)
    dfs(root.Right)
}
复制代码

2. 先序、中序、后序非递归

非递归写法需要借助栈实现(Golang 落泪)
可以采用第一次遇到先标记,再次遇到再处理的方式来统一三种遍历的非递归写法
其模板如下:

func traversal(root *TreeNode) []int {
    if root == nil {
        return []int{}
    }
    stack := []*TreeNode{root}
    res := []int{}
    for len(stack) != 0 {
        // pop 
        node := stack[len(stack)-1]
        stack = stack[:len(stack)-1]
        if node != nil {
            // !!!注意这和正常的是反着来的
            // 比如前序根左右,这里就右 左 当前节点
            
            // 如果是后序,就放这
            // stack = append(stack, node)
            // stack = append(stack, nil)
            if node.Right != nil {
                stack = append(stack, node.Right)
            }
            
            // 如果是中序,就放这
            // stack = append(stack, node)
            // stack = append(stack, nil)
            if node.Left != nil {
                stack = append(stack, node.Left)
            }

            // 当第一次遇到该节点,push 进栈,同时 push nil 作为标记
            stack = append(stack, node)
            stack = append(stack, nil)
        } else {
            // 遇到 push 进的 nil,再 pop 一个才是真正的节点
            node = stack[len(stack)-1]
            stack = stack[:len(stack)-1]
            res = append(res, node.Val)
        }
    }
    return res
}
复制代码

145. 二叉树的后序遍历

/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
func postorderTraversal(root *TreeNode) []int {
	stack := []*TreeNode{}
	res := make([]int, 0)
	if root != nil {
		stack = append(stack, root)
	}
	for len(stack) != 0 {
		node := stack[len(stack)-1]
		stack = stack[:len(stack)-1]
		if node != nil {
			// 倒着插入,后序左右中,倒着中右左
			stack = append(stack, node)
			stack = append(stack, nil)

			if node.Right != nil {
				stack = append(stack, node.Right)
			}

			if node.Left != nil {
				stack = append(stack, node.Left)
			}
		} else {
			node = stack[len(stack)-1]
			stack = stack[:len(stack)-1]
			res = append(res, node.Val)
		}
	}
	return res
}
复制代码

3. 层次遍历

由于前、中、后序需要借助栈实现,可以借助程序的调用栈,所以可以用递归,而层序遍历是借助队列实现,所以没有递归写法
层次遍历的过程是从上至下,从左到右访问所有节点
其模板如下:

func levelOrder(root *TreeNode) [][]int {
    if root == nil {
        return [][]int{}
    }
    queue := []*TreeNode{root}
    res := [][]int{}
    for len(queue) != 0 {
        size := len(queue)
        layer := []int{}
        // 遍历一层,如果不需要层信息,也可以不要这个 for
        for i := 0; i < size; i++ {
            // pop
            node := queue[0]
            queue = queue[1:]

            layer = append(layer, node.Val)
            // push 子树
            if node.Left != nil {
                queue = append(queue, node.Left)
            }
            if node.Right != nil {
                queue = append(queue, node.Right)
            }
        }
        res = append(res, layer)
    }
    return res
}
复制代码

102. 二叉树的层序遍历

/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
func levelOrder(root *TreeNode) [][]int {
    queue := []*TreeNode{}
    res := [][]int{}
    if root != nil {
        queue = append(queue, root)
    }
    for len(queue) > 0 {
        size := len(queue)
        ans := []int{}
        for i := 0; i < size; i++ {
            node := queue[i]
            ans = append(ans, node.Val)
            if node.Left != nil {
                queue = append(queue, node.Left)
            }
            if node.Right != nil {
                queue = append(queue, node.Right)
            }
        }
        queue = queue[size:]
        res = append(res, ans)
    }
    return res
}
复制代码

3. 常用技巧

1. 遍历

先序遍历是自顶向下处理的,在当前层内逻辑,可以认为父节点已经处理完成,常用场景是通过参数传到子树
后序遍历是自底向上处理的,在当前层内逻辑,可以认为子节点已经处理完成,常用场景是根据返回值或值节点本身的值统计汇总交给上层处理

2. 搜素一条边和搜素一棵树

当递归函数有返回值
搜素一条边的写法如下:

if 递归函数(root.Left) != nil {
    return
}
if 递归函数(root.Right) != nil {
    return
}
复制代码

搜索整棵树的写法如下:

left := 递归函数(root.Left)
right := 递归函数(root.Right)
left与right的逻辑处理
复制代码

4. 常见考点

1. 二叉树的属性

101. 对称二叉树

前序遍历,比较左儿子的左与右儿子的右, 和左儿子的右与右儿子的左

/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
func isSymmetric(root *TreeNode) bool {
    if root == nil {
        return true
    }
    return dfs(root.Left, root.Right)
}

func dfs(left, right *TreeNode) bool {
    if left == nil && right == nil {
        return true
    }
    if left == nil || right == nil || left.Val != right.Val{
        return false
    }
    return dfs(left.Left, right.Right) && dfs(left.Right, right.Left)
}
复制代码

104. 二叉树的最大深度

层序遍历,每层计数+1

/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
func maxDepth(root *TreeNode) int {
    queue := []*TreeNode{}
    if root != nil {
        queue = append(queue, root)
    }
    depth := 0
    for len(queue) > 0 {
        size := len(queue)
        for i := 0; i < size; i++ {
            node := queue[0]
            queue = queue[1:]
            if node.Left != nil {
                queue = append(queue, node.Left)
            }
            if node.Right != nil {
                queue = append(queue, node.Right)
            }
        }
        depth++
    }
    return depth
}
复制代码

111. 二叉树的最小深度

层序遍历,每层计数,当找到第一个叶子节点时返回深度

/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
func minDepth(root *TreeNode) int {
    queue := []*TreeNode{}
    if root != nil {
        queue = append(queue, root)
    }
    depth := 0
    for len(queue) > 0 {
        size := len(queue)
        depth++
        for i := 0; i < size; i++ {
            node := queue[0]
            queue = queue[1:]
            // 找到叶子节点
            if node.Left == nil && node.Right == nil {
                return depth
            }
            if node.Left != nil {
                queue = append(queue, node.Left)
            }
            if node.Right != nil {
                queue = append(queue, node.Right)
            }
        }
    }
    return depth
}
复制代码

112. 路径总和

前序遍历,每次往下找,就将目标和减去当前节点值,如果到叶子节点发现目标了,就确定剩余和是否和自己值相等,因为可能只有一个解,所以不同遍历结果间使用或

/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
func hasPathSum(root *TreeNode, sum int) bool {
    if root == nil {
        return false
    }
    // 叶子节点
    if root.Left == nil && root.Right == nil {
        return sum == root.Val
    }
    return hasPathSum(root.Left, sum-root.Val) || hasPathSum(root.Right, sum-root.Val) 
}

复制代码

257. 二叉树的所有路径

前序遍历,持续收集路径,到了叶子节点将值加入到结果数组

/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
var res []string
func binaryTreePaths(root *TreeNode) []string {
    res = []string{}
    dfs(root, "")
    return res
}
func dfs(root *TreeNode, path string) {
    if root == nil {
        return
    }
    path = path + strconv.Itoa(root.Val)
    if root.Left == nil && root.Right == nil {
        res = append(res, path)
        return
    }
    dfs(root.Left, path + "->")
    dfs(root.Right, path + "->")
}
复制代码

404. 左叶子之和

前序遍历,左叶子:在左边,且是叶子节点

/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
var sum int
func sumOfLeftLeaves(root *TreeNode) int {
    sum = 0
    dfs(root)
    return sum
}
func dfs(root *TreeNode) {
    if root == nil {
        return
    }
    if root.Left != nil && root.Left.Left == nil && root.Left.Right == nil {
        sum += root.Left.Val
    }
    dfs(root.Left)
    dfs(root.Right)
}
复制代码

513. 找树左下角的值

层序遍历,每行记录最左的值,最终返回最左的值就是要找的值

/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
func findBottomLeftValue(root *TreeNode) int {
    queue := []*TreeNode{}
    if root != nil {
        queue = append(queue, root)
    }
    left := 0
    for len(queue) > 0 {
        size := len(queue)
        for i := 0; i <size; i++ {
            node := queue[0]
            queue = queue[1:]
            // 记录一下最左的值
            if i==0 {
                left = node.Val
            }
            if node.Left != nil {
                queue = append(queue, node.Left)
            }
            if node.Right != nil {
                queue = append(queue, node.Right)
            }
        }
    }
    return left
}
复制代码

236. 二叉树的最近公共祖先

后序遍历,需要从下往上找

/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode {
    // 如果为空,返回空
    // 如果找到了 p 或 q,也返回
    if root == nil || p == root || q == root {
        return root
    }
    left := lowestCommonAncestor(root.Left, p, q)
    right := lowestCommonAncestor(root.Right, p, q)
    // 如果左边没找到,可能是右边找到了
    if left == nil {
        return right
    }
    // 如果右边没找到,可能是左边找到了
    if right == nil {
        return left
    }
    // 如果两边都找到了,那它就是结果
    return root
}

复制代码

2. 构建树

构建树中,如果没有中序无法构造出唯一结果的树
通常做法是:

  1. 在前序/后序中找到根
  2. 用根的值去中序找,切割出左右子树
  3. 左右子树递归构造

105. 从前序与中序遍历序列构造二叉树

/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
// 前序:根左右
// 中序:左根右
func buildTree(preorder []int, inorder []int) *TreeNode {
    if len(preorder) == 0 || len(inorder) == 0 {
        return nil
    }
    // 前序中拿到根,中序中根据根切分左右子树
    rootVal := preorder[0]
    rootIdx := 0
    for i, v := range inorder {
        if v == rootVal {
            rootIdx = i
            break
        }
    }

    root := &TreeNode{Val: rootVal}

    // 切出前序和中序的左子树
    preLeft := preorder[1:rootIdx+1]
    inLeft := inorder[:rootIdx]

    // 切出前序和中序的右子树
    preRight := preorder[rootIdx+1:]
    inRight := inorder[rootIdx+1:]

    // 循环构造
    root.Left = buildTree(preLeft, inLeft)
    root.Right = buildTree(preRight, inRight)

    return root
}
复制代码

106. 从中序与后序遍历序列构造二叉树

/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
// 后序:左右根
// 中序:左根右
func buildTree(inorder []int, postorder []int) *TreeNode {
    if len(inorder) == 0 || len(postorder) == 0 {
        return nil
    }

    // 后序拿到根,中序找到根位置划分左右子树
    rootVal := postorder[len(postorder)-1]
    rootIdx := 0
    for i, v := range inorder {
        if v == rootVal {
            rootIdx = i
            break
        }
    }

    root := &TreeNode{Val: rootVal}

    // 切出中序和后序的左子树
    inLeft := inorder[:rootIdx]
    postLeft := postorder[:rootIdx]

    // 切出中序和后序的右子树
    inRight := inorder[rootIdx+1:]
    postRight := postorder[rootIdx:len(postorder)-1]

    // 循环构造子树
    root.Left = buildTree(inLeft, postLeft)
    root.Right = buildTree(inRight, postRight)

    return root
}   
复制代码

3. 修改树

226. 翻转二叉树

前序后序都行,唯独中序不行

/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
// 后序,从下往上
func invertTree(root *TreeNode) *TreeNode {
    if root == nil {
        return nil
    }
    root.Left, root.Right = root.Right, root.Left
    invertTree(root.Left)
    invertTree(root.Right)
    return root
}
复制代码

617. 合并二叉树

前序遍历,自上而下遍历修改 root1 的值,最后也是返回它的值

/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
func mergeTrees(root1 *TreeNode, root2 *TreeNode) *TreeNode {
    if root1 == nil {
        return root2
    }
    if root2 == nil {
        return root1
    }
    root1.Val += root2.Val
    root1.Left = mergeTrees(root1.Left, root2.Left)
    root1.Right = mergeTrees(root1.Right, root2.Right)
    return root1
}
复制代码

654. 最大二叉树

先序遍历,选个最大的作为根,递归两边下去构造左右子树

/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
func constructMaximumBinaryTree(nums []int) *TreeNode {
    if len(nums) == 0 {
        return nil
    }
    rootVal, rootIdx := findMaxIdxAndVal(nums)
    root := &TreeNode{Val: rootVal}
    root.Left = constructMaximumBinaryTree(nums[:rootIdx])
    root.Right = constructMaximumBinaryTree(nums[rootIdx+1:])
    return root
}
func findMaxIdxAndVal(nums []int) (int, int) {
    max := nums[0]
    idx := 0
    for i, v := range nums {
        if v > max {
            max = v
            idx = i
        }
    }
    return max, idx
}
复制代码

116. 填充每个节点的下一个右侧节点指针

层序遍历,从上到下对每个遇到的节点:

  1. 尝试该节点的左子树指向该节点的右子树
  2. 尝试该节点的右子树指向该节点下一个节点的左子树
/**
 * Definition for a Node.
 * type Node struct {
 *     Val int
 *     Left *Node
 *     Right *Node
 *     Next *Node
 * }
 */
// 层序遍历
func connect(root *Node) *Node {
	queue := []*Node{}
    if root != nil {
        queue = append(queue, root)
    }
    for len(queue) != 0 {
        size := len(queue)
        for i := 0; i < size; i++ {
            // 拿到当前节点
            node := queue[0]
            queue = queue[1:]
            if node.Left != nil {
                // 第一种情况:左儿子指向右儿子
                node.Left.Next = node.Right
                queue = append(queue, node.Left)
            }
            if node.Right != nil {
                // 第二种情况:右儿子指向别人的左儿子
                if node.Next != nil {
                    node.Right.Next = node.Next.Left
                }
                queue = append(queue, node.Right)
            }
        }
    }
    return root
}
复制代码
分类:
后端
标签: