0.前言
二叉树翻转是一道经典的面试编程题,经常出现在各大公司的招聘笔试面试环节。
这里还有个趣事,Homebrew 的作者 Max Howell 某天去 Google 面试,面试官出了一道翻转二叉树的题目,然而 Max Howell 没答上来,结果被拒。面试官的评语是:“我们 90% 的工程师使用您编写的软件,但是您却无法在面试时在白板上写出翻转二叉树这道题,所以滚蛋吧”。
可见,在求职面试过程中,即使你是一位优秀的程序员,如果答不上算法题,那么在算法方面的能力将被面试官认为是不及格的,甚至无法被聘用。
1.问题描述
给定一个二叉树,翻转输出其镜像。
Input:
4
/ \
2 7
/ \ / \
1 3 6 9
Output:
4
/ \
7 2
/ \ / \
9 6 3 1
2.难度等级
Easy。
3.热门指数
★★★★★
极其热门,必须掌握。
出题公司:阿里,腾讯,字节。
4.解题思路
方法一:递归
翻转一个二叉树,直观上看,就是把二叉树的每一层左右顺序倒过来。比如问题中的例子,第三层 1-3-6-9 经过变换后变成了 9-6-3-1,顺序反过来就对了。
再仔细观察一下,对于上面的例子,根结点的左子结点及其所有的子孙结点构成根结点的左子树,同样的,根结点的右子结点及其所有的子孙节点构成根结点的右子树。因此翻转一个二叉树,就是把根结点的左子树翻转一下,同样的把右子树翻转一下,再交换左右子树就可以了。
当然,翻转左子树和右子树的过程和当前翻转二叉树的过程没有区别,就是递归的调用当前的函数就可以了。
因此,翻转二叉树的步骤可总结如下:
(1)交换根结点的左子结点与右子结点。
(2)翻转根结点的左子树(递归调用当前函数)。
(3)翻转根结点的右子树(递归调用当前函数)。
时间复杂度: O(n),其中 n 为二叉树节点的数目。我们会遍历二叉树中的每一个节点,对每个节点而言,我们在常数时间内交换其两棵子树。
空间复杂度: O(n)。使用的空间由递归栈的深度决定,它等于当前节点在二叉树中的高度。在平均情况下,二叉树的高度与节点个数为对数关系,即 O(logn)。而在最坏情况下,树形成链状,空间复杂度为 O(n)。
Golang
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/
func invertTree(root *TreeNode) *TreeNode {
if root == nil {
return root
}
root.Left, root.Right = root.Right, root.Left
invertTree(root.Left)
invertTree(root.Right)
return root
}
C++
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if (root == nullptr) {
return nullptr;
}
TreeNode* l = root->left;
root->left = root->right;
root->right = l;
invertTree(root->left);
invertTree(root->right);
return root;
}
};
方法二:迭代
二叉树反转,实际上是遍历二叉树的每一个结点,对其左右结点进行交换。
递归实现也就是深度优先遍历的方式,那么对应的就是广度优先遍历。
广度优先遍历也就是采用层序遍历,需要使用额外的数据结构队列来存放待遍历的结点。
具体步骤如下:
(1)首先把二叉树的根结点送入队列。
(2)访问队首结点,把它的左子结点和右子结点分别入队列,然后交换其左右子结点,最后队首结点出队列。
(3)重复上面两步操作,直至队列空。
时间复杂度: O(n),其中 n 为二叉树结点的数目。
空间复杂度: O(n)。使用队列保存要处理的结点,队列中待处理的结点最多时为二叉树的最大宽度。最坏情况下,满二叉树的叶子结点构成二叉树的最大宽度为 (n+1)/2。
Golang
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/
func invertTree(root *TreeNode) *TreeNode {
var queue []*TreeNode
if root != nil {
queue = append(queue, root)
}
for len(queue) > 0 {
sz := len(queue)
for ; sz > 0; sz-- {
node := queue[0]
// 出队列
queue = queue[1:]
if node.Left != nil {
queue = append(queue, node.Left)
}
if node.Right != nil {
queue = append(queue, node.Right)
}
// 交换
node.Left, node.Right = node.Right, node.Left
}
}
return root
}
C++
TreeNode* invertTree(TreeNode* root) {
if (root == nullptr) {
return root;
}
queue<TreeNode*> queue;
queue.push(root);
while (!queue.empty()) {
// 取队首结点
TreeNode* cur = queue.front();
queue.pop();
// 左右子结点入队列
if (cur->left != nullptr) queue.push(cur->left);
if (cur->right != nullptr) queue.push(cur->right);
// 交换左右子结点
auto tmp = cur->left;
cur->left = cur->right;
cur->right = tmp;
}
return root;
}