文章目录
- 226. 翻转二叉树
- 题外话
- 思路
- 递归法
- 迭代法:深度优先遍历
- 层序遍历:广度优先遍历
- 拓展
- 总结
226. 翻转二叉树
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
题外话
这道题目是非常经典的题目,也是比较简单的题目(至少一看就会)。
但正是因为这道题太简单,一看就会,一些同学都没有抓住起本质,稀里糊涂的就把这道题目过了。
如果做过这道题的同学也建议认真看完,相信一定有所收获!
思路
我们之前介绍的都是各种方式遍历二叉树,这次要翻转了,感觉还是有点懵逼。
这得怎么翻转呢?
如果要从整个树来看,翻转还真的挺复杂,整个树以中间分割线进行翻转,如图:
可以发现想要翻转它,其实就把每一个节点的左右孩子交换一下就可以了。
关键在于遍历顺序,前中后序应该选哪一种遍历顺序? (一些同学这道题都过了,但是不知道自己用的是什么顺序)
遍历的过程中去翻转每一个节点的左右孩子就可以达到整体翻转的效果。
注意只要把每一个节点的左右孩子翻转一下,就可以达到整体翻转的效果
这道题目使用前序遍历和后序遍历都可以,唯独中序遍历不方便,因为中序遍历会把某些节点的左右孩子翻转了两次!建议拿纸画一画,就理解了
那么层序遍历可以不可以呢?依然可以的!只要把每一个节点的左右孩子翻转一下的遍历方式都是可以的!
递归法
对于二叉树的递归法的前中后序遍历,已经在二叉树:前中后序递归遍历 详细讲解了。
我们下文以前序遍历
为例,看一下翻转的过程:
首先翻转根节点的左右子树,翻转前如下
翻转后如下
注意:现在4
的左孩子是7
而不是2
了,继续递归遍历4
的左孩子,便是遍历7
的左右子树了,翻转前如下
翻转后如下
当左子树全部遍历完成后,才会去遍历右子树,即递归遍历2
,然后翻转2
的左右子树,最终翻转结束,回归到递归最开始的入口调用处。
我们来看一下递归三部曲
:
确定递归函数的参数和返回值
参数就是要传入节点的指针,不需要其他参数了,通常此时定下来主要参数,如果在写递归的逻辑中发现还需要其他参数的时候,随时补充。返回值的话其实也不需要,但是题目中给出的要返回root
节点的指针,可以直接使用题目定义好的函数,所以就函数的返回类型为*TreeNode
。
func invertTree(root *TreeNode) *TreeNode {}
确定终止条件
当前节点为空的时候,就返回
if root == nil {
return nil
}
确定单层递归的逻辑
因为是前序遍历,所以先进行交换左右孩子节点,然后递归翻转左子树,左子树全部递归完成,回归到对应的递归层后,再递归翻转对应层的右子树。
// 前序遍历
root.Left,root.Right = root.Right,root.Left
if root.Left != nil {
invertTree(root.Left)
}
if root.Right != nil{
invertTree(root.Right)
}
基于这递归三步法,代码基本写完,Go
代码如下:
/**
* 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
if root.Left != nil {
invertTree(root.Left)
}
if root.Right != nil{
invertTree(root.Right)
}
return root
}
迭代法:深度优先遍历
二叉树:之前已经介绍过二叉树的前中后三种遍历方式的迭代法了,这里不赘述啦,可以很轻松的写出如下迭代法的代码:
/**
* 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
}
stack := make([]*TreeNode,0)
stack = append(stack,root)
for len(stack) > 0 {
curNode := stack[len(stack) - 1]
stack = stack[0:len(stack) - 1]
curNode.Left,curNode.Right = curNode.Right,curNode.Left
// 注意前序遍历,是先让右节点入栈,这样才能保证右节点后遍历
if curNode.Right != nil {
stack = append(stack,curNode.Right)
}
if curNode.Left != nil{
stack = append(stack,curNode.Left)
}
}
return root
}
层序遍历:广度优先遍历
也就是层序遍历,层数遍历也是可以翻转这棵树的,因为层序遍历也可以把每个节点的左右孩子都翻转一遍,代码如下:
/**
* 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
}
queue := make([]*TreeNode,0)
queue = append(queue,root)
for len(queue) > 0 {
queueSize := len(queue)
for i := 0;i < queueSize ;i++ {
curNode := queue[0]
queue = queue[1:]
// 翻转当前节点的左右孩子
curNode.Left,curNode.Right = curNode.Right,curNode.Left
if curNode.Left != nil {
queue = append(queue,curNode.Left)
}
if curNode.Right != nil {
queue = append(queue,curNode.Right)
}
}
}
return root
}
拓展
文中我指的是递归的中序遍历是不行的,因为使用递归的中序遍历,某些节点的左右孩子会翻转两次。
如果非要使用递归中序的方式写,也可以,如下代码就可以避免节点左右孩子翻转两次的情况:
/**
* 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
}
if root.Left != nil { // 左
invertTree(root.Left)
}
root.Left,root.Right = root.Right,root.Left // 中
if root.Left != nil{ // 注意 这里依然要遍历左孩子,因为中间节点已经翻转了
invertTree(root.Left)
}
return root
}
代码虽然可以,但这毕竟不是真正的递归中序遍历了。
总结
针对二叉树的问题,解题之前一定要想清楚究竟是前中后序遍历,还是层序遍历。
二叉树解题的大忌就是自己稀里糊涂的过了(因为这道题相对简单),但是也不知道自己是怎么遍历的。
这也是造成了二叉树的题目“一看就会,一写就废”的原因。
针对翻转二叉树,我给出递归法、深度优先(迭代法)、广度优先【层序遍历】
多种解法,都是之前我们讲过的写法,融汇贯通一下而已。
大家一定也有自己的解法,但一定要成方法论,这样才能通用,才能举一反三!