翻转二叉树(LeetCode 226)

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(log⁡n)。而在最坏情况下,树形成链状,空间复杂度为 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;
}

参考文献

226. 翻转二叉树 - LeetCode
明星程序员被Google挂掉的故事 - 简书

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值