代码随想录算法训练营Day15
二叉树的层序遍历
给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。
示例 1:
输入:root = [3,9,20,null,null,15,7]
输出:[[3],[9,20],[15,7]]
示例 2:
输入:root = [1]
输出:[[1]]
示例 3:
输入:root = []
输出:[]
提示:
- 树中节点数目在范围
[0, 2000]内 -1000 <= Node.val <= 1000
思路
二叉树的层序遍历类似于图的广度优先遍历,推广到n叉树的层序遍历也是可以的。需要用到队列来进行算法实现:
初始条件
- 新建空数组作为返回值result
- 判定输入的根节点是否为空,若为空,直接返回result
- 新建一个队列queue,类型为二叉树节点(或者指针)。将根节点入队,进入循环中
循环结束条件
如果queue为空,则循环结束。
循环内部逻辑
- 取得当前队列的长度len,表示的是本层的所有节点个数,根据len对这一层的所有节点进行处理
- 将出队节点current的数据val加入result中
- 如果current的左孩子不为空,则将其加入queue中
- 如果current的右孩子不为空,则将其加入queue中
代码实现
func levelOrder(root *TreeNode) [][]int {
result := [][]int{}
queue := []*TreeNode{}
if root != nil {
queue = append(queue, root)
}
for len(queue) > 0 {
line := []int{}
currentLen := len(queue) //取得当前层次的节点数
for i := 0; i < currentLen; i++ {
//根据节点数出队,记录本层的节点信息
head := queue[0]
queue = queue[1:]
//将当前出队节点数据加入这一行的数组中
line = append(line, head.Val)
if head.Left != nil {
queue = append(queue, head.Left)
}
if head.Right != nil {
queue = append(queue, head.Right)
}
}
if len(line) > 0 {
result = append(result, line)
}
}
return result
}
226.翻转二叉树
题目
给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。
示例 1:
输入:root = [4,2,7,1,3,6,9]
输出:[4,7,2,9,6,3,1]
示例 2:
输入:root = [2,1,3]
输出:[2,3,1]
示例 3:
输入:root = []
输出:[]
提示:
- 树中节点数目范围在
[0, 100]内 -100 <= Node.val <= 100
思路
利用二叉树的遍历即可,将访问节点的处理换成交换左右子树即可。
代码实现
(迭代形式的前序遍历模板)
func invertTree(root *TreeNode) *TreeNode {
// 根为空直接返回
if root == nil{
return nil
}
stack := []*TreeNode{root}
for len(stack) > 0 {
//取栈顶元素
node := stack[len(stack) - 1]
stack = stack[:len(stack) - 1];
//交换左右子树
node.Right, node.Left = node.Left,node.Right
//继续前序遍历
if node.Right != nil {
stack = append(stack, node.Right)
}
if node.Left != nil {
stack = append(stack, node.Left)
}
}
return root
}
101. 对称二叉树
题目
给你一个二叉树的根节点 root , 检查它是否轴对称。
示例 1:
输入:root = [1,2,2,3,4,4,3]
输出:true
示例 2:
输入:root = [1,2,2,null,3,null,3]
输出:false
提示:
- 树中节点数目在范围
[1, 1000]内 -100 <= Node.val <= 100
思路
递归实现
确定递归函数的参数和返回值
每一轮要比较的是根节点的两个子树是否是相互翻转的,进而判断这个树是不是对称树,所以要比较的是两个树,参数自然也是左子树节点和右子树节点。
判定是或者不是,返回值用bool型即可。
确定终止条件
理想情况下,输入的两个子树节点在整个树中的位置都是对称的,根据left和right是否为nil可以分类为:
- left为空,righ不为空:显然不对称,返回false
- left不为空,righ为空:显然不对称,返回false
- left、righ都为空:显然对称,返回true
- left、righ都不为空:
- left.val和right.val不相等:显然不对称,返回false
- left.val和right.val相等:当前节点对称,最终结果要根据下一层的判定结果得出
确定单层递归的逻辑
上面提到,理想情况下,输入的两个子树节点在整个树中的位置都是对称的,我们可以从最简单的情况开始推演递归的流程
第一轮对比的是根节点的两个孩子节点,假设当前判定两个节点的值都相同,则需要继续判定才能得出结论。那么接下来应该应该比较那些节点呢,显然接下来需要检查的还是对称位置的两个节点,不难发现,接下来的需要比较的应该比较的是left的左孩子和right的右孩子(外侧节点),以及left的右孩子和right的左孩子(内侧节点)。
推广到任意两个正在比较的节点(分别记为left和right),由于left和right对称,则left的左孩子和right的右孩子、left的右孩子和right的左孩子的位置都分别成对对称。如果left和right这两个节点都非空且值相同,那么就需要继续递归确定上述的两对子节点才可以得出结论。只有两对子节点的比较结果都是对称,才表示left和right分出去的子树都是对称的。
迭代实现
从递归方式的流程可以看出,每轮对比的其实就是两个对称节点,如果当前对比的节点对称,则需要准备对比下一轮,这和层序遍历很类似,因此可以用队列来实现。
迭代方式
每轮还是对比两个节点,如果提前遇到判定为不对称的情况,则可以直接返回false;但如果是遇到两者为空,迭代方法不能直接返回true,因为和递归方式不同,迭代时两者为空是局部结果且不能重新利用。
如果当前节点非空且值相等,那么就需要继续比较,此时就需要把需要继续比较的节点放入到队列中。
迭代结束条件
提前判定为不对称的情况和递归方法相同;判定为对称则需要让所有对比都顺利匹配成功,此时队列会为空,可以返回true。
代码实现
递归方法
// 递归比较方法
func compare(left, right *TreeNode) bool {
if left == nil && right != nil { //left为空,righ不为空:显然不对称,返回false
return false
} else if left != nil && right == nil { //left不为空,righ为空:显然不对称,返回false
return false
} else if left == nil && right == nil { //left、righ都为空:显然对称,返回true
return true
} else if left.Val != right.Val { //left.val和right.val不相等:显然不对称,返回false
return false
}
//left.val和right.val相等:当前节点对称,但最终结果要根据下一层的判定结果得出
outside := compare(left.Left, right.Right)
inside := compare(left.Right, right.Left)
return outside && inside
}
func isSymmetric(root *TreeNode) bool {
if root == nil {
return true
}
return compare(root.Left, root.Right)
}
迭代方法
func isSymmetric(root *TreeNode) bool {
if root == nil {
return true
}
queue := []*TreeNode{root.Left, root.Right}
for len(queue) > 0 {
//每次出队两个节点进行对比
left := queue[0]
right := queue[1]
queue = queue[2:]
//都为空可以提前结束此轮迭代
if left == nil && right == nil {
continue
}
//提前判定不对称的情况和递归相同
if left == nil || right == nil || left.Val != right.Val {
return false
}
//如果对比节点非空且值相同,则将需要比较的组合依次加入队列中
queue = append(queue, left.Left, right.Right, left.Right, right.Left)
}
//如果能顺利匹配所有节点,说明是对称的
return true
}