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;
}
};
总结
- 核心技巧:入栈顺序反着来
- 前序遍历要求:根 →→ 左 →→ 右。
- 栈是后进先出,想让左节点先出来,就得后入栈。
- 所以代码逻辑:先压右,再压左。
- 复杂度
- 时间 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;
}
};
总结
- 核心技巧:倒过来的前序遍历
- 后序要求:左 →→ 右 →→ 根。
- 逆向思维:先做"根 →→ 右 →→ 左"的遍历,最后把结果数组整体反转。
- 操作变化:为了得到"根 →→ 右 →→ 左",入栈时需先左后右(保证右节点先出栈)。
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;
}
};
总结
-
核心总结
-
左路压栈:遇到节点先压栈,一直往左走,直到无路可走。
-
出栈处理:栈顶出栈,立即记录值,然后转向右子树。
-
模拟递归:栈保存了"回溯"的路径,保证了 左 -> 根 -> 右 的顺序。
-
复杂度
- 时间复杂度: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;
}
};
总结
-
核心总结
-
队列特性:利用队列 先进先出 (FIFO) 特性,保证按从左到右的顺序处理节点。
-
层快照:循环前必须用
size变量记录队列长度,这代表了当前层的节点数。 -
逐层剥离:内部
for循环处理当前层并加入下一层节点,实现了层级分隔。 -
复杂度
- 时间复杂度:O(N)
- 每个节点只会进队一次、出队一次。
- 空间复杂度:O(N)
- 队列存储的节点数量最多为树的最大宽度(叶子节点总数),最坏情况下为 N。