文章目录
- dfs深搜
- 说明和基础概念:
- 模板
- 分治法以及应用
- 归并排序:
- 快速排序:
- 1、 [104. 二叉树的最大深度](https://leetcode.cn/problems/maximum-depth-of-binary-tree/)
- 2、[110. 平衡二叉树](https://leetcode.cn/problems/balanced-binary-tree/)
- 3、[124. 二叉树中的最大路径和](https://leetcode.cn/problems/binary-tree-maximum-path-sum/)
- 4、[236. 二叉树的最近公共祖先](https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/description/)
- 5、[701. 二叉搜索树中的插入操作](https://leetcode.cn/problems/insert-into-a-binary-search-tree/description/)
- BFS层序遍历
- 二叉树
dfs深搜
说明和基础概念:
说明:预计一刷时间:7小时,这个文章建议你可以直接复制粘贴,然后打印出来
dfs是图的一种搜索算法,每一个可能的分支路径深入到不能再深入为止,且每个节点只能访问一次。
它们的区别跟关联都在于它们的数据结构,回溯算法是树结构,深度优先搜索是图结构,dfs用栈,bfs用队列
递归和迭代区别:
递归是重复调用函数自身实现循环。迭代是函数内某段代码实现循环
没有找到什么好的介绍和开始dfs的地方,怎么说呢,就是它作为一种算法比较分散
栈可以实现dfs深搜,然后dfs算法又是属于图,而二叉树又属于特殊的图,所以二叉树数据结构实现了很多dfs算法,也有不是二叉树,而是属于图的dfs算法,因为算法:指解题方案的准确而完整的描述,是一系列解决问题的清晰指令,算法代表着用系统的方法描述解决问题的策略机制
二叉树基本上可以分成BFS和DFS这两种以及正常用前中后层次遍历,穿插交错
其中dfs的分治法用的很多
比较好的是:https://greyireland.gitbook.io/algorithm-pattern/shu-ju-jie-gou-pian/binary_tree
声明:本篇文章有很多是借鉴这个链接的,但是介绍概念和说明等部分和题解的注释部分是我个人理解写的,绝对没有什么可借鉴的,必须要自己的理解才可以写出来的,所以大家可以结合这学习
里边的所涉及到的dfs在二叉树部分和栈与队列部分,
所以你刷dfs等于是刷了部分的二叉树,和栈的一部分
这有个视频,感觉也不错,但是我没看,16分钟:
https://www.bilibili.com/video/BV1Qa411t7NA/?spm_id_from=333.337.search-card.all.click&vd_source=46a8aeb0bd2b7168a7bd0fbe5d3532f4
模板
DFS 深度搜索-从上到下
看着熟悉不?这妥妥的回溯思想,不过是套到了二叉树结构
毕竟回溯和dfs思想特别像,但是又有区别
type TreeNode struct {
Val int
Left *TreeNode
Right *TreeNode
}
func preorderTraversal(root *TreeNode) []int {
result := make([]int, 0)
dfs(root, &result)
return result
}
// V1:深度遍历,结果指针作为参数传入到函数内部
func dfs(root *TreeNode, result *[]int) {
if root == nil {
return
}
*result = append(*result, root.Val)
dfs(root.Left, result)
dfs(root.Right, result)
}
DFS 深度搜索-从下向上(分治法)
// V2:通过分治法遍历
func preorderTraversal(root *TreeNode) []int {
result := divideAndConquer(root)
return result
}
func divideAndConquer(root *TreeNode) []int {
result := make([]int, 0)
// 返回条件(null & leaf)
if root == nil {
return result
}
// 分治(Divide)
left := divideAndConquer(root.Left)
right := divideAndConquer(root.Right)
// 合并结果(Conquer)
result = append(result, root.Val)
result = append(result, left...)
result = append(result, right...)
return result
}
注意点:
DFS 深度搜索(从上到下) 和分治法区别:前者一般将最终结果通过指针参数传入,后者一般递归返回结果最后合并
分治法以及应用
因为分治法用的太多咱们再了解一点:
先分别处理局部,再合并结果
适用场景
- 快速排序
- 归并排序
- 二叉树相关问题
分治法模板
- 递归返回条件
- 分段处理
- 合并结果
归并排序:
func MergeSort(nums []int) []int {
return mergeSort(nums)
}
func mergeSort(nums []int) []int {
if len(nums) <= 1 {
return nums
}
// 分治法:divide 分为两段
mid := len(nums) / 2
left := mergeSort(nums[:mid])
right := mergeSort(nums[mid:])
// 合并两段数据
result := merge(left, right)
return result
}
func merge(left, right []int) (result []int) {
// 两边数组合并游标
l := 0
r := 0
// 注意不能越界
for l < len(left) && r < len(right) {
// 谁小合并谁
if left[l] > right[r] {
result = append(result, right[r])
r++
} else {
result = append(result, left[l])
l++
}
}
// 剩余部分合并
result = append(result, left[l:]...)
result = append(result, right[r:]...)
return
}
快速排序:
func QuickSort(nums []int) []int {
// 思路:把一个数组分为左右两段,左段小于右段,类似分治法没有合并过程
quickSort(nums, 0, len(nums)-1)
return nums
}
// 原地交换,所以传入交换索引
func quickSort(nums []int, start, end int) {
if start < end {
// 分治法:divide
pivot := partition(nums, start, end)
quickSort(nums, 0, pivot-1)
quickSort(nums, pivot+1, end)
}
}
// 分区
func partition(nums []int, start, end int) int {
p := nums[end]
i := start
for j := start; j < end; j++ {
if nums[j] < p {
swap(nums, i, j)
i++
}
}
// 把中间的值换为用于比较的基准值
swap(nums, i, end)
return i
}
func swap(nums []int, i, j int) {
t := nums[i]
nums[i] = nums[j]
nums[j] = t
}
1、 104. 二叉树的最大深度
给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明: 叶子节点是指没有子节点的节点。
3
/ \
9 20
/ \
15 7
这是第二次写,
不同的是这次使用的go写的,用的也是分治思想
相比labuladong的解法更为精简
一个字,妙
/**
* 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
}
//分
left := maxDepth(root.Left) // 注意left和right是一个int类型的数,同时这是一个初始化变量,
right := maxDepth(root.Right)
//合
if left > right {
return left + 1
}
return right + 1
}
2、110. 平衡二叉树
给定一个二叉树,判断它是否是高度平衡的二叉树。
本题中,一棵高度平衡二叉树定义为:
一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/
func isBalanced(root *TreeNode) bool {
if maxDepth(root) == -1 {
return false
}
return true
}
func maxDepth(root *TreeNode) int {
//返回条件处理
if root == nil {
return 0
}
//fen
left := maxDepth(root.Left)
right := maxDepth(root.Right)
//前两个条件判断叶子节点会不会出现,高度差超过1,后两个条件就不用说了哈
if left == -1 || right == -1 || left-right > 1 || right-left > 1 {
return -1
}
if left > right {
return left + 1
}
return right + 1
}
3、124. 二叉树中的最大路径和
路径 被定义为一条从树中任意节点出发,沿父节点-子节点连接,达到任意节点的序列。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点,且不一定经过根节点。
type ResultType struct {
SinglePath int // 保存单边最大值
MaxPath int // 保存最大值(单边或者两个单边+根的值)
}
func maxPathSum(root *TreeNode) int {
result := helper(root)
return result.MaxPath
}
func helper(root *TreeNode) ResultType {
// check
if root == nil {
return ResultType{
SinglePath: 0,
MaxPath: -(1 << 31),
}
}
// Divide
left := helper(root.Left)
right := helper(root.Right)
// Conquer
result := ResultType{}
// 求单边最大值
if left.SinglePath > right.SinglePath {
result.SinglePath = max(left.SinglePath + root.Val, 0)
} else {
result.SinglePath = max(right.SinglePath + root.Val, 0)
}
// 求两边加根最大值
maxPath := max(right.MaxPath, left.MaxPath)
result.MaxPath = max(maxPath,left.SinglePath+right.SinglePath+root.Val)
return result
}
func max(a,b int) int {
if a > b {
return a
}
return b
}
4、236. 二叉树的最近公共祖先
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
看了题解自己带入才懂…
func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode {
// nil
// 首先当给的root是nil时候判断一下,最近公共祖先就是他自己nil,也是root
if root == nil {
return root
}
// 相等 直接返回Root节点
// 带入,当一开始第一个函数执行时候,root是p或者q,那么就是最本的根了,结果只能是他自己,返回root
if root == p || root == q {
return root
}
// 分
left := lowestCommonAncestor(root.Left, p, q)
right := lowestCommonAncestor(root.Right, p, q)
// 合
// 1. 第一个判断,带入5,1(5,0当然也可以,这时候right要向下找,最后right返回的还是0,而left自然是5,满足合的第一条件),试一下就知道,left为5,right为1,两边不为空,所以返回root为3
if left != nil && right != nil {
return root
}
// 下一个判断和下下一个判断原理相同一起说了(当其中一方为空了),你带入5,4试一下,你会发现righ为nil
// 这时候就直接返回5了,也就是left为5,right为空,因为右边是1,他往下一直找找找,但是5和4都没有找到,最后一直返回空空空,所以right是nil而left是5,所以最终返回了5为最近公共祖先
if left != nil {
return left
}
if right != nil {
return right
}
return nil
}
5、701. 二叉搜索树中的插入操作
给定二叉搜索树(BST)的根节点 root
和要插入树中的值 value
,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据 保证 ,新值和原始二叉搜索树中的任意节点值都不同。
注意,可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。 你可以返回 任意有效的结果 。
func insertIntoBST(root *TreeNode, val int) *TreeNode {
if root == nil {
root = &TreeNode{Val: val}
return root
}
if root.Val > val {
root.Left = insertIntoBST(root.Left, val)
} else {
root.Right = insertIntoBST(root.Right, val)
}
return root
}
BFS层序遍历
模板:
func levelOrder(root *TreeNode) [][]int {
// 通过上一层的长度确定下一层的元素
result := make([][]int, 0)
if root == nil {
return result
}
queue := make([]*TreeNode, 0)
queue = append(queue, root)
for len(queue) > 0 {
list := make([]int, 0)
// 为什么要取length?
// 记录当前层有多少元素(遍历当前层,再添加下一层)
l := len(queue)
for i := 0; i < l; i++ {
// 出队列
level := queue[0]
queue = queue[1:]
list = append(list, level.Val)
if level.Left != nil {
queue = append(queue, level.Left)
}
if level.Right != nil {
queue = append(queue, level.Right)
}
}
result = append(result, list)
}
return result
}
1、102. 二叉树的层序遍历
给你二叉树的根节点 root
,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。
在了解队列的特性后,我们理解这题目的方式就是,带入一个节点去试一下,就懂了
声明res–> 判断root为空情况–>初始化节点型切片,就是队列–>给队列追加root,第一个节点–>第一个for循环遍历,看len(queue)–>初始化暂时的list, --> 拿到queue长度–>第二层for循环–>出队列,拿到level–> list追加list–>入队–> res追加list–> 最后return res
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/
func levelOrder(root *TreeNode) [][]int {
res := make([][]int, 0)
if root == nil {
return res
}
queue := make([]*TreeNode, 0) // 初始化一个TreeNode型切片
queue = append(queue, root) // 给队列加上root节点
for len(queue) > 0 {
list := make([]int, 0)
// 为什么要取length?
// 记录当前层有多少元素(指的是节点) (遍历当前层, 再添加下一层)
l := len(queue) // queue的长度是怎么算的?
for i := 0; i < l; i++ {
level := queue[0]
queue = queue[1:] // 这两步出队列
list = append(list, level.Val) // 然后取出节点的值,赋值给list
if level.Left != nil { // 如果当前节点左子节点存在,则赋给queue队列的下一个
queue = append(queue, level.Left)
}
if level.Right != nil { // 这个也是同样的道理,从左向右
queue = append(queue, level.Right)
}
}
res = append(res,list) // 给结果res加上临时的list
}
return res
}
2、107. 二叉树的层序遍历 II
给你二叉树的根节点 root
,返回其节点值 自底向上的层序遍历 。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)
- 和102题差不多,只不过是结果反着来了,
- 先写个levelOrder函数,思路、声明res–> 判断root为空情况–>初始化节点型切片,就是队列–>给队列追加root,第一个节点–>第一个for循环遍历,看len(queue)–>初始化暂时的list, --> 拿到queue长度–>第二层for循环–>出队列,拿到level–> list追加list–>入队–> res追加list–> 最后return res
- 再写一个reverser反转切片元素函数,给levelOrder反转就可以了
func levelOrderBottom(root *TreeNode) [][]int {
res := levelOrder(root)
reverse(res)
return res
}
func reverse(nums [][]int) {
for i,j := 0, len(nums)-1; i < j; i, j = i+1, j-1 {
nums[i], nums[j] = nums[j], nums[i]
}
}
func levelOrder(root *TreeNode) [][]int {
res := make([][]int, 0)
if root == nil {
return res
}
queue := make([]*TreeNode, 0)
queue = append(queue, root)
for len(queue) > 0 {
list := make([]int, 0)
l := len(queue)
for i := 0; i < l; i++ {
level := queue[0]
queue = queue[1:]
list = append(list, level.Val)
if level.Left != nil {
queue = append(queue, level.Left)
}
if level.Right != nil {
queue = append(queue, level.Right)
}
}
res = append(res, list)
}
return res
}
3、103. 二叉树的锯齿形层序遍历
给你二叉树的根节点 root
,返回其节点值的 锯齿形层序遍历 。(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)。
和102题比较,知道,只需要加一个层序控制器toogle,用来判断list是否需要反转后再让res追加即可
func zigzagLevelOrder(root *TreeNode) [][]int {
res := make([][]int, 0)
if root == nil {
return res
}
queue := make([]*TreeNode, 0)
queue = append(queue, root)
// 第一层for循环控制的层序遍历的层序
toogle := false // 是否反转的触发器
for len(queue) > 0 {
list := make([]int, 0)
l := len(queue)
for i := 0; i < l; i++ {
level := queue[0]
queue = queue[1:]
list = append(list, level.Val)
if level.Left != nil {
queue = append(queue, level.Left)
}
if level.Right != nil {
queue = append(queue, level.Right)
}
}
if toogle { // 奇数层为true,需要反转list
reverser(list)
}
res = append(res, list)
toogle = !toogle
}
return res
}
func reverser(nums []int) {
for i,j := 0, len(nums)-1; i < j; i,j = i+1,j-1 {
nums[i], nums[j] = nums[j], nums[i]
}
}
二叉树
1、98. 验证二叉搜索树
给你一个二叉树的根节点 root
,判断其是否是一个有效的二叉搜索树。
有效 二叉搜索树定义如下:
- 节点的左子树只包含 小于 当前节点的数。
- 节点的右子树只包含 大于 当前节点的数。
- 所有左子树和右子树自身必须也是二叉搜索树。
中序递归
- 通过一个inOrder函数从底层开始把他们通过中序遍历以此放入切片result中
- inOrder中要先判断给的root是不是nil,这是一种情况不能忘记
- 通过 for 循环遍历判断即可
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/
func isValidBST(root *TreeNode) bool {
result := make([]int, 0)
inOrder(root, &result)
// 检查有序
for i := 0; i < len(result) - 1; i++ {
if result[i] >= result[i+1] {
return false
}
}
return true
}
func inOrder(root *TreeNode, result *[]int) {
if root == nil {
return
}
inOrder(root.Left, result)
*result = append(*result, root.Val)
inOrder(root.Right, result)
}