代码随想录算法训练营 Day12 | 二叉树 part02

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)。

相关题

  • 100.相同的树(opens new window)

    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);
        }
    };
  • 572.另一个树的子树

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)。
相关推荐
gihigo19982 小时前
距离角度解耦法的MIMO-OFDM雷达波束形成及优化MATLAB实现
开发语言·算法·matlab
2401_853576502 小时前
代码自动生成框架
开发语言·c++·算法
逆境不可逃2 小时前
【从零入门23种设计模式23】行为型之模板模式
java·开发语言·算法·设计模式·职场和发展·模板模式
ZPC82102 小时前
PPO 在ROS2 中训练与推理
人工智能·算法·机器人
IronMurphy2 小时前
【算法二十五】105. 从前序与中序遍历序列构造二叉树 236. 二叉树的最近公共祖先
java·数据结构·算法
2401_853576502 小时前
C++中的组合模式变体
开发语言·c++·算法
像污秽一样2 小时前
算法设计与分析-习题8.2
数据结构·算法·排序算法·dfs·化简
玛卡巴卡ldf2 小时前
【LeetCode 手撕算法】(子串) 560-和为 K 的子数组
java·数据结构·算法·leetcode
CoovallyAIHub2 小时前
BMW GenAI4Q:每57秒下线一辆车,AI如何为每辆车定制专属质检清单
数据库·算法·架构