226.翻转二叉树(二叉树算法题)
226.翻转二叉树
力扣题目链接(opens new window)
翻转一棵二叉树。
这道题目背后有一个让程序员心酸的故事,听说 Homebrew的作者Max Howell,就是因为没在白板上写出翻转二叉树,最后被Google拒绝了。(真假不做判断,全当一个乐子哈)
算法公开课
《代码随想录》算法视频公开课 (opens new window):听说一位巨佬面Google被拒了,因为没写出翻转二叉树 | LeetCode:226.翻转二叉树 (opens new window),相信结合视频再看本篇题解,更有助于大家对本题的理解。
#题外话
这道题目是非常经典的题目,也是比较简单的题目(至少一看就会)。
但正是因为这道题太简单,一看就会,一些同学都没有抓住起本质,稀里糊涂的就把这道题目过了。
如果做过这道题的同学也建议认真看完,相信一定有所收获!
#思路
我们之前介绍的都是各种方式遍历二叉树,这次要翻转了,感觉还是有点懵逼。
这得怎么翻转呢?
如果要从整个树来看,翻转还真的挺复杂,整个树以中间分割线进行翻转,如图:
可以发现想要翻转它,其实就把每一个节点的左右孩子交换一下就可以了。
关键在于遍历顺序,前中后序应该选哪一种遍历顺序? (一些同学这道题都过了,但是不知道自己用的是什么顺序)
遍历的过程中去翻转每一个节点的左右孩子就可以达到整体翻转的效果。
注意只要把每一个节点的左右孩子翻转一下,就可以达到整体翻转的效果
这道题目使用前序遍历和后序遍历都可以,唯独中序遍历不方便,因为中序遍历会把某些节点的左右孩子翻转了两次!建议拿纸画一画,就理解了
那么层序遍历可以不可以呢?依然可以的!只要把每一个节点的左右孩子翻转一下的遍历方式都是可以的!
递归写法:
/*** Definition for a binary tree node.* 每个 TreeNode 表示二叉树中的一个节点* 包含三个部分:当前节点的值(val),左子节点(left),右子节点(right)*/
public class TreeNode {int val; // 节点的值TreeNode left; // 指向左子树的引用(指针)TreeNode right; // 指向右子树的引用(指针)// 无参构造函数:创建一个空节点(值默认为0,左右子节点为null)TreeNode() {}// 构造函数:创建一个值为 val 的节点,左右子节点默认为 nullTreeNode(int val) { this.val = val; }// 构造函数:创建一个值为 val 的节点,并指定其左、右子节点TreeNode(int val, TreeNode left, TreeNode right) {this.val = val;this.left = left;this.right = right;}
}/*** Solution 类包含解决特定问题的方法* 这里实现的是“翻转二叉树”功能*/
class Solution {/*** invertTree 方法:翻转以 root 为根的二叉树* 翻转的含义是:将每个节点的左右子树互换位置* 例如:* 原树: 4 翻转后: 4* / \ / \* 2 7 7 2* / \ / \ / \ / \* 1 3 6 9 9 6 3 1** @param root 当前二叉树的根节点* @return 翻转后的二叉树的根节点(仍然是原来的 root,但结构已改变)*/public TreeNode invertTree(TreeNode root) {// 基本情况:如果当前节点为空(null),说明到达了叶子节点的子节点// 直接返回 null,不做任何操作if (root == null) {return null;}// 第一步:交换当前节点的左右子节点// 调用自定义的 swapChildren 方法完成交换swapChildren(root);// 第二步:递归翻转左子树// 此时左子树其实是原右子树(因为已经 swap 过)// 但我们在递归调用时仍按“左”“右”逻辑处理invertTree(root.left);// 第三步:递归翻转右子树invertTree(root.right);// 最后返回翻转后的根节点return root;}/*** swapChildren 方法:交换某个节点的左右子节点* 使用一个临时变量来完成两个引用的交换** @param root 需要交换子节点的节点*/public void swapChildren(TreeNode root) {// 创建一个临时变量 temp,保存右子节点的引用TreeNode temp = root.right;// 将左子节点赋值给右子节点的位置root.right = root.left;// 将原来保存在 temp 中的右子节点赋值给左子节点的位置root.left = temp;}
}
不递归:
/*** Definition for a binary tree node.* 二叉树节点的定义类*/
public class TreeNode {int val; // 节点存储的值TreeNode left; // 指向左子节点的引用(指针)TreeNode right; // 指向右子节点的引用(指针)// 无参构造函数:创建一个空节点(val 默认为 0,left 和 right 都为 null)TreeNode() {}// 构造函数:创建一个值为 val 的节点,左、右子节点默认为 nullTreeNode(int val) { this.val = val; }// 构造函数:创建一个值为 val 的节点,并指定其左、右子节点TreeNode(int val, TreeNode left, TreeNode right) {this.val = val;this.left = left;this.right = right;}
}/*** Solution 类:包含解决“翻转二叉树”问题的方法*/
class Solution {/*** invertTree 方法:使用层序遍历(BFS)的方式翻转二叉树* 翻转的含义是:将每个节点的左右子树互换位置,最终得到原树的镜像** 示例:* 原树: 4 翻转后: 4* / \ / \* 2 7 7 2* / \ / \ / \ / \* 1 3 6 9 9 6 3 1** @param root 二叉树的根节点* @return 翻转后的二叉树的根节点(结构已改变,但 root 引用不变)*/public TreeNode invertTree(TreeNode root) {// 如果根节点为空,说明树为空,直接返回 nullif (root == null) {return null;}// 创建一个队列(Queue),用于实现层序遍历(BFS)// LinkedList 实现了 Queue 接口,适合做队列使用Queue<TreeNode> queue = new LinkedList<>();// 将根节点入队,作为遍历的起点queue.offer(root);// 开始 BFS 循环:当队列不为空时,继续处理每一层的节点while (!queue.isEmpty()) {// 获取当前层的节点数量// 这样可以确保我们只处理当前层的所有节点int len = queue.size();// 使用 while 循环处理当前层的每一个节点(共 len 个)while (len-- > 0) {// 从队列中取出一个节点(先进先出)TreeNode node = queue.poll();// 翻转当前节点的左右子树:交换 left 和 right 指针swapChildren(node);// 如果当前节点有左子节点,则将其加入队列,供下一层处理if (node.left != null) {queue.offer(node.left);}// 如果当前节点有右子节点,则将其加入队列,供下一层处理if (node.right != null) {queue.offer(node.right);}}// 当前层处理完毕,进入下一层(while 循环继续,直到队列为空)}// 所有节点都已翻转完毕,返回修改后的根节点return root;}/*** swapChildren 方法:交换某个节点的左右子节点* 使用临时变量完成两个引用的交换** @param root 需要交换左右子节点的节点*/public void swapChildren(TreeNode root) {// 用临时变量 temp 保存右子节点的引用TreeNode temp = root.right;// 将左子节点赋值给右子节点位置root.right = root.left;// 将原来保存的右子节点(temp)赋值给左子节点位置root.left = temp;}
}
总结
针对二叉树的问题,解题之前一定要想清楚究竟是前中后序遍历,还是层序遍历。
二叉树解题的大忌就是自己稀里糊涂的过了(因为这道题相对简单),但是也不知道自己是怎么遍历的。
这也是造成了二叉树的题目“一看就会,一写就废”的原因。