226. 翻转二叉树
给你一棵二叉树的根节点
root,翻转这棵二叉树,并返回其根节点。
cpp
// 递归法 (DFS)
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if(root == NULL) return NULL;
// 1. 交换当前节点的左右子树
swap(root->left, root->right);
// 2. 递归处理左子树
invertTree(root->left);
// 3. 递归处理右子树
invertTree(root->right);
return root;
}
};
// 迭代法 (DFS 栈模拟)
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if(root == NULL) return NULL;
stack<TreeNode*> st;
st.push(root);
while(!st.empty()){
// 1. 弹出节点并交换
TreeNode* node = st.top(); st.pop();
swap(node->left, node->right);
// 2. 右孩子先入栈,左孩子后入栈 (保证出栈时先处理左)
if(node->right) st.push(node->right);
if(node->left) st.push(node->left);
}
return root;
}
};
// 迭代法 (BFS 层序遍历)
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if(root == NULL) return NULL;
queue<TreeNode*> que;
que.push(root);
while(!que.empty()){
int size = que.size(); // 记录当前层节点数
for(int i = 0; i < size; i++){
TreeNode* node = que.front(); que.pop();
// 【核心】交换左右子节点
swap(node->left, node->right);
// 加入下一层节点
if(node->left) que.push(node->left);
if(node->right) que.push(node->right);
}
}
return root;
}
};
总结
1. 核心逻辑
遍历二叉树 -> 访问节点时执行 swap 操作。
2. 方法对比
| 方法 | 核心数据结构 | 特点 | 适用场景 |
|---|---|---|---|
| 递归法 | 系统栈 | 代码最简洁,逻辑最直观 | 首选,面试时优先写出 |
| DFS 迭代 | 显式 stack |
模拟递归过程,深度优先 | 防止递归过深导致栈溢出 |
| BFS 迭代 | 显式 queue |
层序遍历,按层翻转 | 需要按层处理数据时使用 |
3. 复杂度分析
- 时间复杂度:O(N)
- 每个节点都被访问一次,交换操作是 O(1)。
- 空间复杂度:O(N)
- 递归:取决于递归深度(树高),最坏 O(N)。
- DFS 迭代:取决于栈深度(树高),最坏 O(N)。
- BFS 迭代:取决于队列宽度(树的宽度),最坏 O(N)。
101. 对称二叉树
给你一个二叉树的根节点
root, 检查它是否轴对称。
cpp
// 递归法
class Solution {
public:
// 比较函数:判断两棵子树是否互为镜像
bool cmp(TreeNode* left, TreeNode* right) {
// 1. 终止条件处理
if (left && !right) return false; // 左有右无,不对称
else if (!left && right) return false; // 左无右有,不对称
else if (!left && !right) return true; // 左右都无,对称
// 左右都有,但值不同,不对称
else if (left && right && left->val != right->val) return false;
// 2. 单层递归逻辑
// 比较外侧:左子树的左 vs 右子树的右
bool isLeft = cmp(left->left, right->right);
// 比较内侧:左子树的右 vs 右子树的左
bool isRight = cmp(left->right, right->left);
// 只有外侧和内侧都对称,整棵树才对称
return isLeft && isRight;
}
bool isSymmetric(TreeNode* root) {
if (root == NULL) return true;
// 从根节点的左右子节点开始比较
return cmp(root->left, root->right);
}
};
// 迭代法 (使用栈)
class Solution {
public:
bool isSymmetric(TreeNode* root) {
if (root == NULL) return true;
stack<TreeNode*> st;
// 1. 初始化:成对入栈 (根的左节点 和 根的右节点)
st.push(root->left);
st.push(root->right);
while (!st.empty()) {
// 2. 成对取出
TreeNode* left = st.top(); st.pop();
TreeNode* right = st.top(); st.pop();
// 3. 比较逻辑
if (!left && !right) continue; // 两节点都为空,说明这一对是对称的,继续下一对
if (!left || !right || left->val != right->val) return false; // 不满足条件,直接返回false
// 4. 成对入栈:注意顺序,保证外侧比外侧,内侧比内侧
// 外侧:左节点的左孩子 vs 右节点的右孩子
st.push(left->left);
st.push(right->right);
// 内侧:左节点的右孩子 vs 右节点的左孩子
st.push(left->right);
st.push(right->left);
}
return true;
}
};
总结
1. 核心逻辑
- 比较两棵子树根节点是否相等。
- 比较 外侧节点 是否对称(左左 vs 右右)。
- 比较 内侧节点 是否对称(左右 vs 右左)。
2. 方法对比
| 方法 | 核心机制 | 逻辑流程 | 特点 |
|---|---|---|---|
| 递归法 | 系统栈 | 深度优先,先到底层再向上返回结果 | 代码简洁,符合直觉,面试首选 |
| 迭代法 | 显式栈/队列 | 广度优先,逐层成对比较 | 显式控制遍历过程,可避免递归栈溢出 |
3. 复杂度分析
- 时间复杂度:O(N)
- 每个节点都会被访问一次并进行比较,遍历整棵树。
- 空间复杂度:O(N)
- 递归法:空间消耗取决于递归深度(树高),最坏情况 O(N)。
- 迭代法:栈/队列存储节点数量,最坏情况 O(N)。
相关题
-
cpp
class Solution { public: // 辅助函数:递归比较两棵子树是否相同 bool cmp(TreeNode* left, TreeNode* right) { // 1. 终止条件 // 若两者都为空,则视为相同,返回 true if (!left && !right) return true; // 若一个为空一个不为空,或者节点值不同,则不相同,返回 false // 注意:这里逻辑合并了,先判断了非空,再判断值 else if (!left || !right || left->val != right->val) return false; // 2. 单层递归逻辑 // 【关键】比较左子树的左孩子 与 右子树的左孩子(对应位置比较) bool isleft = cmp(left->left, right->left); // 比较左子树的右孩子 与 右子树的右孩子(对应位置比较) bool isright = cmp(left->right, right->right); // 只有左右子树都相同,整棵树才相同 return isleft && isright; } // 主函数 bool isSameTree(TreeNode* p, TreeNode* q) { // 先判断根节点情况(与 cmp 函数逻辑类似,作为入口) if (!p && !q) return true; else if (!p || !q || p->val != q->val) return false; // 根节点判断通过后,递归判断左右子树 return cmp(p->left, q->left) && cmp(p->right, q->right); } };
cpp
class Solution {
public:
// 辅助函数:判断两棵树是否完全相同
bool cmp(TreeNode* left, TreeNode* right) {
// 1. 终止条件
if (!left && !right) return true; // 两者都为空,相等
// 一个为空一个不为空,或者值不同,不相等
else if (!left || !right || left->val != right->val) return false;
// 2. 递归比较:对应位置必须完全一致
return cmp(left->left, right->left) && cmp(left->right, right->right);
}
// 主函数:判断 subRoot 是否是 root 的子树
bool isSubtree(TreeNode* root, TreeNode* subRoot) {
// 1. 特殊情况处理
// 题目通常保证 subRoot 非空,但为了鲁棒性,若 root 遍历到了空节点还没匹配上,则 false
if (!root || !subRoot) return false;
// 2. 核心逻辑:两步走
// (1) 以当前 root 为根的树,是否和 subRoot 完全相同?
if (cmp(root, subRoot)) return true;
// (2) 如果当前根不匹配,则递归去左子树或右子树中寻找
// 只要任意一边找到了,就返回 true
return isSubtree(root->left, subRoot) || isSubtree(root->right, subRoot);
}
};
104. 二叉树的最大深度
给定一个二叉树
root,返回其最大深度。二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。
cpp
// 递归法 (DFS)
class Solution {
public:
int maxDepth(TreeNode* root) {
// 1. 终止条件:空节点高度为 0
if (root == NULL) return 0;
// 2. 单层递归逻辑
// 探求左子树的最大深度
int left = maxDepth(root->left);
// 探求右子树的最大深度
int right = maxDepth(root->right);
// 3. 返回结果:当前树深度 = 左右深度的最大值 + 1 (当前节点)
return max(left, right) + 1;
}
};
// 迭代法 (BFS 层序遍历)
class Solution {
public:
int maxDepth(TreeNode* root) {
if (root == NULL) return 0;
queue<TreeNode*> que;
int ans = 0;
que.push(root);
while (!que.empty()) {
int size = que.size(); // 记录当前层节点数
// 遍历当前层
for (int i = 0; i < size; i++) {
TreeNode* node = que.front(); que.pop();
if (node->left) que.push(node->left);
if (node->right) que.push(node->right);
}
ans++; // 遍历完一层,深度+1
}
return ans;
}
};
总结
1. 核心逻辑对比
| 方法 | 核心思想 | 遍历顺序 | 适用场景 |
|---|---|---|---|
| 递归法 (DFS) | 分治法:求子问题高度,推导当前高度 | 后序遍历 (左右根) | 代码极简,面试首选 |
| 迭代法 (BFS) | 层序遍历:数一数有多少层 | 层序遍历 (广度优先) | 直观理解,适合需要按层处理数据的场景 |
2. 复杂度分析
- 时间复杂度:O(N)
- 两种方法都需要遍历所有节点,每个节点只访问一次。
- 空间复杂度:
- 递归法:O(N)。空间取决于递归栈深度,最坏情况(链表)为 N。
- 迭代法:O(N)。空间取决于队列存储的节点数,最坏情况(满二叉树最后一层)接近 N。
相关题
cpp
class Solution {
public:
int maxDepth(Node* root) {
// 1. 终止条件:空节点深度为 0
if (root == NULL) return 0;
int dep = 0;
// 2. 遍历所有子节点
// 递归求出每一个子树的最大深度,并更新 dep 为最大值
for (Node* i : root->children) {
dep = max(dep, maxDepth(i));
}
// 3. 返回结果
// 当前节点深度 = 子树最大深度 + 1 (当前节点本身)
return dep + 1;
}
};
111. 二叉树的最小深度
给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
说明:叶子节点是指没有子节点的节点。
cpp
// 递归法 (DFS)
class Solution {
public:
int getDepth(TreeNode* root) {
// 1. 终止条件:空节点深度为 0
if (root == NULL) return 0;
int left = getDepth(root->left);
int right = getDepth(root->right);
// 2. 关键逻辑:处理单子树情况
// 如果左子树为空,右子树不为空,最小深度由右子树决定
if (!root->left && root->right) return right + 1;
// 如果右子树为空,左子树不为空,最小深度由左子树决定
else if (root->left && !root->right) return left + 1;
// 3. 正常情况:左右子树都有,取较小值
return min(left, right) + 1;
}
int minDepth(TreeNode* root) {
return getDepth(root);
}
};
// 迭代法 (BFS 层序遍历)
class Solution {
public:
int minDepth(TreeNode* root) {
if (root == NULL) return 0;
queue<TreeNode*> que;
int ans = 0;
que.push(root);
while (!que.empty()) {
int size = que.size();
ans++; // 进入新的一层,深度+1
for (int i = 0; i < size; i++) {
TreeNode* node = que.front(); que.pop();
// 【核心】判断是否为叶子节点
// 一旦遇到叶子节点,直接返回当前深度,这就是最小的
if (node->left == NULL && node->right == NULL) return ans;
if (node->left) que.push(node->left);
if (node->right) que.push(node->right);
}
}
return ans;
}
};
总结
1. 核心区别:最小深度的定义陷阱
误区:直接套用最大深度模板写 return min(left, right) + 1。
原因:最小深度定义为根到最近叶子节点的距离。
- 如果一个节点只有右孩子(左为空),它的左子树深度为 0。
- 若取
min(0, right),结果是 1,但这并不是到"叶子节点"的距离,因为该节点本身不是叶子。真正的路径应该走右子树那边。
2. 方法对比
| 方法 | 逻辑 | 优点 | 缺点 |
|---|---|---|---|
| 递归法 (DFS) | 后序遍历,自底向上汇总高度 | 代码结构统一,无需队列容器 | 逻辑稍复杂,必须单独处理单子树情况 |
| 迭代法 (BFS) | 层序遍历,找到即返回 | 效率高,不需要遍历整棵树,找到答案立即停止 | 空间复杂度取决于树的最大宽度 |
3. 复杂度分析
- 时间复杂度:O(N)
- 最坏情况都需要遍历所有节点。
- 空间复杂度:O(N)
- 递归栈深度或队列宽度,最坏均为 O(N)。