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. 构建树
构建树中,如果没有中序无法构造出唯一结果的树
通常做法是:
- 在前序/后序中找到根
- 用根的值去中序找,切割出左右子树
- 左右子树递归构造
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. 填充每个节点的下一个右侧节点指针
层序遍历,从上到下对每个遇到的节点:
- 尝试该节点的左子树指向该节点的右子树
- 尝试该节点的右子树指向该节点下一个节点的左子树
/**
* 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
}