LeetCode 226. 翻转二叉树
摘要
LeetCode 226 翻转二叉树题解,使用迭代法(栈)前序遍历实现二叉树翻转,时间复杂度 O(n),空间复杂度 O(n)。本文同时讨论前序、后序遍历可行而中序遍历不可行的原因。
标签
#算法 #LeetCode #二叉树 #栈 #迭代 #题解
目录
-
题目描述
-
易错点
-
思路
- 前序遍历可行
- 后序遍历可行
- 中序遍历陷阱
-
编写代码
1. 题目描述
给你一棵二叉树的根节点 root,翻转这棵二叉树,并返回其根节点。
示例 1:
text
ini
输入:root = [4,2,7,1,3,6,9]
输出:[4,7,2,9,6,3,1]
示例 2:
text
ini
输入:root = [2,1,3]
输出:[2,3,1]
示例 3:
text
ini
输入:root = []
输出:[]
提示:
- 树中节点数目范围是
[0, 100] -100 <= Node.val <= 100
2. 易错点
- 空树直接返回
NULL。 - 翻转时需交换左右子树的指针,注意使用临时变量保存。
- 迭代法中,栈存储节点指针,访问顺序需符合遍历要求。
- 释放动态分配的栈内存,避免内存泄漏。
- 中序遍历无法正确翻转,需避免使用。
3. 思路
翻转二叉树的本质是交换每个节点的左右子树。任何遍历顺序只要能保证每个节点的左右子树都被交换且不重复、不遗漏即可。但不同遍历顺序的效果不同。
前序遍历可行
前序遍历顺序:根 → 左 → 右。
- 访问根节点时,交换其左右子树。
- 然后遍历左子树(此时左子树已经是原右子树,但尚未被处理),再遍历右子树(原左子树)。
- 由于每个节点在其子节点被处理之前就已经完成了交换,子节点指针已经正确指向新的子树,因此后续遍历会正确处理所有节点。
- 每个节点恰好被交换一次。
后序遍历可行
后序遍历顺序:左 → 右 → 根。
- 先递归处理左子树和右子树,使它们自身完成翻转。
- 然后交换当前节点的左右子树。
- 这样每个节点也只被交换一次,且子树的翻转在交换前完成,逻辑正确。
中序遍历陷阱
中序遍历顺序:左 → 根 → 右。
- 假设当前节点为
root,先遍历左子树(root->left)。 - 然后访问
root,交换root->left和root->right。 - 此时
root->right变成了原来的左子树(已经被遍历过),而root->left变成了原来的右子树(尚未被遍历)。 - 接着中序遍历会去遍历右子树,也就是
root->right(注意此时root->right已经是原来的左子树),但该子树已经在前面的步骤中被完整遍历过了。 - 而原来的右子树(现在变成了
root->left)却永远不会被遍历,因为中序遍历已经经过了左子树(在交换前),现在不会再回头。 - 结果就是:原来左子树被翻转了两次(一次在遍历时,一次在作为右子树被处理时?实际上它是被重复遍历了但未被正确翻转),而原来的右子树完全没有被翻转。
具体过程示例(以节点 2 为根,左子树 1,右子树 3):
- 中序递归:先处理左子树 1(无子节点,返回)。
- 访问根 2,交换左右 -> 左=3,右=1。
- 然后中序遍历右子树,即 node->right = 1,但 1 已经被处理过了(且已返回),不会再次翻转。
- 结果是节点 3 从未被处理,因此 3 的子树(如果有)不会被翻转。
所以中序遍历在翻转二叉树时会导致部分节点被遗漏或重复处理,无法得到正确结果。因此,通常采用前序或后序遍历。
4. 编写代码
采用前序遍历迭代法(栈实现):
c 运行
c
ini
struct TreeNode* invertTree(struct TreeNode* root) {
if (root == NULL) return NULL;
struct TreeNode** stack = (struct TreeNode**)malloc(10000 * sizeof(struct TreeNode*));
int top = 0;
stack[top++] = root;
while (top > 0) {
struct TreeNode* node = stack[--top];
struct TreeNode* temp = node->left;
node->left = node->right;
node->right = temp;
if (node->left) stack[top++] = node->left;
if (node->right) stack[top++] = node->right;
}
free(stack);
return root;
}