226. Invert Binary Tree
题干简述:
翻转一个二叉树。
翻转:交换二叉树的左右子节点位置,根节点不会动。
解题思路
看完题目后,我心想,不愧是简单难度的题目,着实简单。利用递归,交换节点左右子树不就可以了。自信的我直接写出了如下所示的代码:
var invertTree = function(root) {
const revert = (node) => {
if (!node) return null;
node.left = revert(node.right);
node.right = revert(node.left);
return node;
}
revert(root);
return root;
};
Run code 之后,却发现出现了一个奇怪的结果:
简单分析一下出现的结果,我们会发现根节点 4 的左右子节点交换后,左子节点变成了之前的右子节点 7,但是现在的右子节点依旧是 7,这是为什么呢?难道是逻辑没有执行吗?显然,并不是这样。让我们重新来看一下一个二叉树通过怎样的数据结构展示的。在这里,是通过链表的形式,每个节点的左右子树,都是通过存储一个指针指向其在内存中的位置来得到的。我们在直接通过 node.left = revert(node.right);
设置了左子节点后,此时存储的指针已经发生了变化,指向的是之前的右子节点;所以在后面的 node.right = revert(node.left);
执行时,这里的 node.left 的值是和 node.right 相等的,故而还是 7。
为了解决这个问题,我们需要在交换前对节点先做一次存储,这也是涉及到链表的题目中的常见操作。
题解
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {TreeNode}
*/
var invertTree = function(root) {
const revert = (node) => {
if (!node) return null;
const temp = node.left;
node.left = revert(node.right);
node.right = revert(temp);
return node;
}
revert(root);
return root;
};
思考
这道题很简单,但是仔细想想的话,其实还是蛮有嚼头的。上面我的解法非常简单,其本质就是二叉树的前序遍历,也就是先处理根节点,再通过递归去处理根节点的两个子节点;换一种思路,先递归处理根节点的两个子节点,再处理根节点,一样的效果,不过这个时候就变成了后序遍历了。为什么这里不用中序遍历呢?原因在于如果使用中序遍历,先处理左子树,再处理根节点,到这里都是ok的,但是在处理完根节点后,此时原本的右子树会被替换为先前的左子树的值,此时再递归处理右子树,相当于还是在处理之前的左子树,会导致丢失原本的右子树数据。
除了通过递归,也就是 DFS 深度优先的算法外,我们还可以通过 Queue 队列 BFS 广度优先遍历的方式实现。实现思路简单来说,就是通过一个数组维护一个根节点的队列,每当有根节点出列时,交换它的左右子树,并将左右子树的根节点入列;重复这个操作,直到队列为空时即完成了翻转。
这里是 BFS 的实现,大家可以参考一下。
var invertTree = function(root) {
if (!root) return null;
const queue = [root]; // 通过数组维护队列
// while 循环
while(queue.length) {
const temp = queue.shift(); // 出列
[temp.left, temp.right] = [temp.right, temp.left]; // 交换左右子节点位置
if (temp.left) queue.push(temp.left);
if (temp.right) queue.push(temp.right); // 交换后的左右子节点入列
}
return root;
};
Q.E.D.