给定一个二叉树,返回它的 后序 遍历。
示例:
输入: [1,null,2,3]
1
\
2
/
3
输出: [3,2,1]
class Solution {
public:
void postorder(TreeNode * root, vector<int> &res) {
if(!root) return;
postorder(root->left, res);
postorder(root->right, res);
res.push_back(root->val);
}
vector<int> postorderTraversal(TreeNode* root) {
vector<int>res;
postorder(root, res);
return res;
}
};
二叉树前中后序的递归版本属于easy题,而迭代版本通常是medium甚至是hard。
在做迭代版本之前,我建议大家先问问各类“遍历”算法的本质是什么?是最后输出的那一串有序的数字吗?数字的顺序是对的,遍历算法就是对的吗?
个人认为,以上问题的答案都应该是:否。“遍历”的本质是对内存的有序访问,失去了访问顺序,即便你用各种数据结构恢复了这个次序,遍历本身也显得毫无意义。常见的后序遍历写法中有一种已经出现在评论区了——它的思想也很简单,大家做过单词串翻转吗?
String in = "it is a word"
String out = "word a is it"
这个问题有一种很优雅的写法是先reverse(in),然后再逐词翻转——“两次反转”,最后得到的就是正确顺序。
回到二叉树后序遍历,你也可以利用这种思想,利用双向链表的addFirst,对外部次序和内含次序进行同时翻转,同样会得到一种非常”优雅”的解法,结构简单明晰,甚至还有点好背(狗头)。但是,它并没有真正实现“遍历”——仔细看会发现,该算法其实在使用一种异构的前序遍历:“中->右->左”,而非传统意义上的“中->左->右”,而这种异构也正是他的第一次反转。而第二次反转就在输出序列上。
所以本质上,这是一个“前序遍历”,而不是所谓的“后序遍历”。只有当你的各个节点以“左->右->中”的次序依次出现在迭代的loop当中时,它才是真正的后序遍历,就像官解那样。贴个我个人的版本。
class Solution {
public:
void postorder(TreeNode* root, stack<int> &s){
if(!root) return;
s.push(root->val);
postorder(root->right, s);
postorder(root->left, s);
}
vector<int> postorderTraversal(TreeNode* root) {
stack<int>s;
vector<int>res;
postorder(root, s);
while(!s.empty())
{
res.push_back(s.top());
s.pop();
}
return res;
}
};