代码随想录算法训练营 Day11 | 二叉树 part01

144. 二叉树的前序遍历

给你二叉树的根节点 root ,返回它节点值的 前序遍历。

cpp 复制代码
class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        // 1. 边界条件处理:如果根节点为空,直接返回空数组
        if(root == NULL) return {};
        // 2. 初始化数据结构
        stack<TreeNode*> st; // 栈用于模拟递归调用栈
        vector<int> vec;     // 存储遍历结果
        // 3. 根节点入栈,作为遍历的起点
        st.push(root);
        // 4. 开始循环,直到栈为空(说明遍历结束)
        while(!st.empty()){
            // 4.1 弹出栈顶元素
            // 栈顶元素即为当前应该访问的"根"节点
            TreeNode* node = st.top();
            st.pop();
            // 4.2 访问节点:将节点值加入结果数组
            // 前序遍历顺序:根 -> 左 -> 右
            vec.push_back(node->val);
            // 4.3 右子节点入栈(如果存在)
            // 关键点:栈是后进先出(LIFO)结构
            // 因为要先处理左子树,所以必须让右子节点先入栈,沉到底部
            if(node->right) st.push(node->right);
            // 4.4 左子节点入栈(如果存在)
            // 左子节点后入栈,会位于栈顶,下次循环会被优先处理
            if(node->left) st.push(node->left);
        }
        return vec;
    }
};

总结

  1. 核心技巧:入栈顺序反着来
  • 前序遍历要求:根 →→ 左 →→ 右。
  • 栈是后进先出,想让左节点先出来,就得后入栈。
  • 所以代码逻辑:先压右,再压左。
  1. 复杂度
  • 时间 O(N) 每个节点进出栈各一次。
  • 空间 O(H) H 是树的高度。

145. 二叉树的后序遍历

给你一棵二叉树的根节点 root ,返回其节点值的 后序遍历 。

cpp 复制代码
class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        // 1. 边界条件:空树直接返回
        if(root == NULL) return {};
        stack<TreeNode*> st;
        vector<int> vec;
        st.push(root);
        while(!st.empty()){
            TreeNode* node = st.top();
            st.pop();
            // 2. 记录节点值
            // 此时逻辑变成了:根 -> 右 -> 左
            vec.push_back(node->val);
            // 3. 入栈顺序调整
            // 因为栈是后进先出,想让右节点先处理,就得先压左节点
            // 这与前序遍历的入栈顺序正好相反
            if(node->left) st.push(node->left);
            if(node->right) st.push(node->right);
        }
        // 4. 关键一步:反转结果
        // 将"根 -> 右 -> 左"翻转为"左 -> 右 -> 根",即后序遍历
        reverse(vec.begin(), vec.end());
        return vec;
    }
};

总结

  1. 核心技巧:倒过来的前序遍历
  • 后序要求:左 →→ 右 →→ 根。
  • 逆向思维:先做"根 →→ 右 →→ 左"的遍历,最后把结果数组整体反转。
  • 操作变化:为了得到"根 →→ 右 →→ 左",入栈时需先左后右(保证右节点先出栈)。

2. 复杂度

  • 时间 O(N) 遍历加反转,总耗时仍是线性。
  • 空间 O(H) 取决于树的高度。

94. 二叉树的中序遍历

给定一个二叉树的根节点 root ,返回 它的 中序 遍历

cpp 复制代码
class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        if(root==NULL) return {};
        stack<TreeNode*> st;
        vector<int> vec;
        TreeNode* cur = root; // 1. 初始化当前指针指向根节点
        // 循环条件:当前节点不为空 或 栈不为空
        while(cur || !st.empty()){
            if(cur){
                // 阶段一:一路向左走到底,将路径上的节点入栈
                st.push(cur);
                cur = cur->left;
            }
            else{
                // 阶段二:左边走到头了,处理栈顶节点
                cur = st.top(); st.pop(); // 弹出栈顶(最近访问的父节点)
                vec.push_back(cur->val);  // 【关键】加入结果集(左 -> 根)
                cur = cur->right;         // 转向右子树,开始新一轮的"左走到底"
            }
        }
        return vec;
    }
};

总结

  1. 核心总结

  2. 左路压栈:遇到节点先压栈,一直往左走,直到无路可走。

  3. 出栈处理:栈顶出栈,立即记录值,然后转向右子树。

  4. 模拟递归:栈保存了"回溯"的路径,保证了 左 -> 根 -> 右 的顺序。

  5. 复杂度

  • 时间复杂度:O(N)
    • 每个节点会被访问一次(压栈一次、出栈一次),无冗余操作。
  • 空间复杂度:O(N)
    • 栈的空间取决于树的高度。
    • 最坏情况(链表形状):树高为 N,栈需存储所有节点,空间 O(N)。
    • 最好情况(平衡二叉树):树高为 logN,空间 O(logN)。

102. 二叉树的层序遍历

给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。

cpp 复制代码
class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        if(root==NULL) return {};
        queue<TreeNode*> que;
        vector<vector<int>> ans;
        que.push(root);
        while(!que.empty()){
            int size = que.size(); // 1. 关键点:获取当前层的节点数量
            vector<int> vec;
            // 2. 只循环当前层的数量,处理完一层
            for(int i = 0; i < size; i++){
                TreeNode* node = que.front(); que.pop(); // 队头出队
                vec.push_back(node->val); // 记录节点值
                // 3. 左右子节点依次入队(下一层的节点)
                if(node->left) que.push(node->left);
                if(node->right) que.push(node->right);
            }
            ans.push_back(vec); // 将当前层的结果加入总答案
        }
        return ans;
    }
};

总结

  1. 核心总结

  2. 队列特性:利用队列 先进先出 (FIFO) 特性,保证按从左到右的顺序处理节点。

  3. 层快照:循环前必须用 size 变量记录队列长度,这代表了当前层的节点数。

  4. 逐层剥离:内部 for 循环处理当前层并加入下一层节点,实现了层级分隔。

  5. 复杂度

  • 时间复杂度:O(N)
    • 每个节点只会进队一次、出队一次。
  • 空间复杂度:O(N)
    • 队列存储的节点数量最多为树的最大宽度(叶子节点总数),最坏情况下为 N。

相关题

相关推荐
笨笨饿23 分钟前
34_数据结构_栈
c语言·开发语言·数据结构·人工智能·嵌入式硬件·算法
im_AMBER35 分钟前
Leetcode 152 被围绕的区域 | 岛屿数量
数据结构·算法·leetcode·深度优先·广度优先·图搜索算法
吕司1 小时前
LeetCode Hot Code——最大子数组和
数据结构·算法·leetcode
海清河晏1111 小时前
数据结构 | 单链表
数据结构·unity·dreamweaver
会编程的土豆2 小时前
【数据结构与算法】拓扑排序2
数据结构·算法·leetcode
来自远方的老作者2 小时前
第7章 运算符-7.5 比较运算符
开发语言·数据结构·python·算法·代码规范·比较运算符
圣光SG2 小时前
数据结构通用笔记(语言无关)
数据结构·学习·链表·数组··队列
郝学胜-神的一滴2 小时前
「栈与缩点的艺术」二叉树前序序列化合法性判定:从脑筋急转弯到工程实现
java·开发语言·数据结构·c++·python·算法
汀、人工智能3 小时前
[特殊字符] 第25课:合并两个有序链表
数据结构·算法·链表·数据库架构··合并两个有序链表
计算机安禾3 小时前
【数据结构与算法】第30篇:哈希表(Hash Table)
数据结构·学习·算法·哈希算法·散列表·visual studio