144. Binary Tree Preorder Traversal**
https://leetcode.com/problems/binary-tree-preorder-traversal/
题目描述
Given a binary tree, return the preorder traversal of its nodes’ values.
Example:
Input: [1,null,2,3]
1
\
2
/
3
Output: [1,2,3]
Follow up: Recursive solution is trivial, could you do it iteratively?
C++ 实现 1
前序遍历的递归形式.
class Solution {
private:
vector<int> res;
void preorder(TreeNode *root) {
if (!root) return;
res.push_back(root->val);
preorder(root->left);
preorder(root->right);
}
public:
vector<int> preorderTraversal(TreeNode* root) {
preorder(root);
return res;
}
};
C++ 实现 2
使用栈来完成前序遍历, 首先将 root 节点压入栈中, 再将其弹出, 弹出的过程就完成了对根节点的遍历, 之后为了完成 “先遍历左子树, 再遍历右子树”, 考虑到栈的 “先入后出” 的特性, 此时应该先将 root 的右孩子压入栈中, 然后将左孩子压入栈中.
另外一个问题, 为啥会想到栈呢 ? 首先, 常用的数据结构就那么一些, 队列 (用于层序遍历), 栈 … 再考虑到 DFS 方法遍历树时, 递归调用函数的底层实现也是用栈完成的.
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
if (!root) return {};
vector<int> res;
stack<TreeNode*> st;
st.push(root);
while (!st.empty()) {
auto root = st.top();
st.pop();
res.push_back(root->val);
if (root->right) st.push(root->right);
if (root->left) st.push(root->left);
}
return res;
}
};
C++ 实现 3
前序遍历的迭代形式. 前序遍历, 中序遍历(参见94. Binary Tree Inorder Traversal**)和后序遍历(145. Binary Tree Postorder Traversal***)都需要用到栈. 为了方便解析, 我先给出全部代码, 然后再来解析:
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
if (!root) return {};
vector<int> res;
stack<TreeNode*> st;
TreeNode *p = root;
while (!st.empty() || p) {
if (p) {
res.push_back(p->val);
st.push(p);
p = p->left;
} else {
auto cur = st.top();
st.pop();
p = cur->right;
}
}
return res;
}
};
用 p
来表示当前访问的节点, 如果把 while 循环中关于栈的代码给抽离, 看看形式:
if (p) {
res.push_back(p->val);
p = p->left;
} else {
p = cur->right;
}
如果再去对比中序遍历的代码(参见 94. Binary Tree Inorder Traversal**), 更容易清楚的感受到, 这段代码和递归的形式差不多, 前序遍历先访问当前节点, 再访问左右子树. 如果当前节点不为空, 那么就入栈 (联想递归), 入栈相当于要保存当前函数的状态, 然后安心的去访问左子树. 当左子树访问完, 需要访问右子树, 此时应该从栈中恢复上一层函数的状态. 于是就变成了:
if (p) {
res.push_back(p->val);
st.push(p);
p = p->left;
} else {
auto cur = st.top();
st.pop();
p = cur->right;
}
错误代码!!!
从错误代码中进行学习也是有必要的, 这个 bug 花了我一点时间才找到… 真的一不小心就容易出现偏差.
先看代码:
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
if (!root) return {};
vector<int> res;
stack<TreeNode*> st;
TreeNode *p = root;
while (!st.empty() || p) {
if (p) {
res.push_back(p->val);
st.push(p);
p = p->left;
} else {
auto p = st.top();
st.pop();
p = p->right;
}
}
return res;
}
};
这个实现和 C++ 实现 2
中的代码看起来没有太大区别, 主要不同点如果不仔细看的话, 很难发现出来. 考验一下, 你现在还能看出两段代码的差别吗?
🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣
🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣
揭露答案:
区别在:
// C++ 实现 2 中的代码
auto cur = st.top();
st.pop();
p = cur->right;
// 本段代码
auto p = st.top();
st.pop();
p = p->right;
这会造成 bug, 因为 while
循环中需要利用 p
来判断是否跳出, 这个 p
是 while 外面定义的, 但是本段代码中的
auto p = st.top();
用局部变量覆盖了外面定义的 p
… 尴尬, 不仔细看真的很难发现.