Binary Tree Postorder Traversal

Tags: Tree, Stack, Hard

Question

Problem Statement

Given a binary tree, return the postorder traversal of its nodes’ values.

For example:
Given binary tree {1,#,2,3},

  1. 1
  2. \
  3. 2
  4. /
  5. 3

return [3,2,1].

Note: Recursive solution is trivial, could you do it iteratively?

题解1 - 递归

首先使用递归便于理解。

Python - Divide and Conquer

  1. # Definition for a binary tree node.
  2. # class TreeNode:
  3. # def __init__(self, x):
  4. # self.val = x
  5. # self.left = None
  6. # self.right = None
  7. class Solution:
  8. # @param {TreeNode} root
  9. # @return {integer[]}
  10. def postorderTraversal(self, root):
  11. if root is None:
  12. return []
  13. else:
  14. return self.postorderTraversal(root.left) +\
  15. self.postorderTraversal(root.right) + [root.val]

C++ - Traversal

  1. /**
  2. * Definition of TreeNode:
  3. * class TreeNode {
  4. * public:
  5. * int val;
  6. * TreeNode *left, *right;
  7. * TreeNode(int val) {
  8. * this->val = val;
  9. * this->left = this->right = NULL;
  10. * }
  11. * }
  12. */
  13. class Solution {
  14. /**
  15. * @param root: The root of binary tree.
  16. * @return: Postorder in vector which contains node values.
  17. */
  18. public:
  19. vector<int> postorderTraversal(TreeNode *root) {
  20. vector<int> result;
  21. traverse(root, result);
  22. return result;
  23. }
  24. private:
  25. void traverse(TreeNode *root, vector<int> &ret) {
  26. if (root == NULL) {
  27. return;
  28. }
  29. traverse(root->left, ret);
  30. traverse(root->right, ret);
  31. ret.push_back(root->val);
  32. }
  33. };

Java - Divide and Conquer

  1. /**
  2. * Definition for a binary tree node.
  3. * public class TreeNode {
  4. * int val;
  5. * TreeNode left;
  6. * TreeNode right;
  7. * TreeNode(int x) { val = x; }
  8. * }
  9. */
  10. public class Solution {
  11. public List<Integer> postorderTraversal(TreeNode root) {
  12. List<Integer> result = new ArrayList<Integer>();
  13. if (root != null) {
  14. List<Integer> left = postorderTraversal(root.left);
  15. result.addAll(left);
  16. List<Integer> right = postorderTraversal(root.right);
  17. result.addAll(right);
  18. result.add(root.val);
  19. }
  20. return result;
  21. }
  22. }

Java - Traversal

  1. /**
  2. * Definition for a binary tree node.
  3. * public class TreeNode {
  4. * int val;
  5. * TreeNode left;
  6. * TreeNode right;
  7. * TreeNode(int x) { val = x; }
  8. * }
  9. */
  10. public class Solution {
  11. private List<Integer> result = new ArrayList<Integer>();
  12. public List<Integer> postorderTraversal(TreeNode root) {
  13. if (root != null) {
  14. postorderTraversal(root.left);
  15. postorderTraversal(root.right);
  16. result.add(root.val);
  17. }
  18. return result;
  19. }
  20. }

源码分析

递归版的太简单了,没啥好说的,注意入栈顺序。

复杂度分析

时间复杂度近似为 O(n).

题解2 - 迭代

使用递归写后序遍历那是相当的简单,我们来个不使用递归的迭代版。整体思路仍然为「左右根」,那么怎么才能知道什么时候该访问根节点呢?问题即转化为如何保证左右子节点一定先被访问到?由于入栈之后左右节点已无法区分,因此需要区分左右子节点是否被访问过(加入到最终返回结果中)。除了有左右节点的情况,根节点也可能没有任何子节点,此时也可直接将其值加入到最终返回结果中。

Python

  1. # Definition for a binary tree node.
  2. # class TreeNode:
  3. # def __init__(self, x):
  4. # self.val = x
  5. # self.left = None
  6. # self.right = None
  7. class Solution:
  8. # @param {TreeNode} root
  9. # @return {integer[]}
  10. def postorderTraversal(self, root):
  11. result = []
  12. if root is None:
  13. return result
  14. s = []
  15. # previously traversed node
  16. prev = None
  17. s.append(root)
  18. while s:
  19. curr = s[-1]
  20. noChild = curr.left is None and curr.right is None
  21. childVisited = (prev is not None) and \
  22. (curr.left == prev or curr.right == prev)
  23. if noChild or childVisited:
  24. result.append(curr.val)
  25. s.pop()
  26. prev = curr
  27. else:
  28. if curr.right is not None:
  29. s.append(curr.right)
  30. if curr.left is not None:
  31. s.append(curr.left)
  32. return result

C++

  1. /**
  2. * Definition for a binary tree node.
  3. * struct TreeNode {
  4. * int val;
  5. * TreeNode *left;
  6. * TreeNode *right;
  7. * TreeNode(int x) : val(x), left(NULL), right(NULL) {}
  8. * };
  9. */
  10. class Solution {
  11. public:
  12. vector<int> postorderTraversal(TreeNode* root) {
  13. vector<int> result;
  14. if (root == NULL) return result;
  15. TreeNode *prev = NULL;
  16. stack<TreeNode *> s;
  17. s.push(root);
  18. while (!s.empty()) {
  19. TreeNode *curr = s.top();
  20. bool noChild = false;
  21. if (curr->left == NULL && curr->right == NULL) {
  22. noChild = true;
  23. }
  24. bool childVisited = false;
  25. if (prev != NULL && (curr->left == prev || curr->right == prev)) {
  26. childVisited = true;
  27. }
  28. // traverse
  29. if (noChild || childVisited) {
  30. result.push_back(curr->val);
  31. s.pop();
  32. prev = curr;
  33. } else {
  34. if (curr->right != NULL) s.push(curr->right);
  35. if (curr->left != NULL) s.push(curr->left);
  36. }
  37. }
  38. return result;
  39. }
  40. };

Java

  1. /**
  2. * Definition for a binary tree node.
  3. * public class TreeNode {
  4. * int val;
  5. * TreeNode left;
  6. * TreeNode right;
  7. * TreeNode(int x) { val = x; }
  8. * }
  9. */
  10. public class Solution {
  11. public List<Integer> postorderTraversal(TreeNode root) {
  12. List<Integer> result = new ArrayList<Integer>();
  13. Deque<TreeNode> stack = new ArrayDeque<TreeNode>();
  14. if (root != null) stack.push(root);
  15. TreeNode prev = null;
  16. while (!stack.isEmpty()) {
  17. TreeNode curr = stack.peek();
  18. boolean noChild = (curr.left == null && curr.right == null);
  19. boolean childVisited = false;
  20. if (prev != null && (curr.left == prev || curr.right == prev)) {
  21. childVisited = true;
  22. }
  23. if (noChild || childVisited) {
  24. curr = stack.pop();
  25. result.add(curr.val);
  26. prev = curr;
  27. } else {
  28. if (curr.right != null) stack.push(curr.right);
  29. if (curr.left != null) stack.push(curr.left);
  30. }
  31. }
  32. return result;
  33. }
  34. }

源码分析

遍历顺序为『左右根』,判断根节点是否应该从栈中剔除有两种条件,一为无子节点,二为子节点已遍历过。判断子节点是否遍历过需要排除prev == null 的情况,因为 prev 初始化为 null. Java 中在 while 内部新建 curr 变量而不是复用 root 有一定性能提升,不知是不是 JVM 运行时优化导致的。

将递归写成迭代的难点在于如何在迭代中体现递归本质及边界条件的确立,可使用简单示例和纸上画出栈调用图辅助分析。

复杂度分析

最坏情况下栈内存储所有节点,空间复杂度近似为 O(n), 每个节点遍历两次或以上,时间复杂度近似为 O(n).

题解3 - 反转先序遍历

要想得到『左右根』的后序遍历结果,我们发现只需将『根右左』的结果转置即可,而先序遍历通常为『根左右』,故改变『左右』的顺序即可,所以如此一来后序遍历的非递归实现起来就非常简单了。

C++

  1. /**
  2. * Definition of TreeNode:
  3. * class TreeNode {
  4. * public:
  5. * int val;
  6. * TreeNode *left, *right;
  7. * TreeNode(int val) {
  8. * this->val = val;
  9. * this->left = this->right = NULL;
  10. * }
  11. * }
  12. */
  13. class Solution {
  14. /**
  15. * @param root: The root of binary tree.
  16. * @return: Postorder in vector which contains node values.
  17. */
  18. public:
  19. vector<int> postorderTraversal(TreeNode *root) {
  20. vector<int> result;
  21. if (root == NULL) return result;
  22. stack<TreeNode*> s;
  23. s.push(root);
  24. while (!s.empty()) {
  25. TreeNode *node = s.top();
  26. s.pop();
  27. result.push_back(node->val);
  28. // root, right, left => left, right, root
  29. if (node->left != NULL) s.push(node->left);
  30. if (node->right != NULL) s.push(node->right);
  31. }
  32. // reverse
  33. std::reverse(result.begin(), result.end());
  34. return result;
  35. }
  36. };

Java

  1. /**
  2. * Definition for a binary tree node.
  3. * public class TreeNode {
  4. * int val;
  5. * TreeNode left;
  6. * TreeNode right;
  7. * TreeNode(int x) { val = x; }
  8. * }
  9. */
  10. public class Solution {
  11. public List<Integer> postorderTraversal(TreeNode root) {
  12. List<Integer> result = new ArrayList<Integer>();
  13. Deque<TreeNode> stack = new ArrayDeque<TreeNode>();
  14. if (root != null) stack.push(root);
  15. while (!stack.isEmpty()) {
  16. TreeNode curr = stack.pop();
  17. result.add(curr.val);
  18. if (curr.left != null) stack.push(curr.left);
  19. if (curr.right != null) stack.push(curr.right);
  20. }
  21. Collections.reverse(result);
  22. return result;
  23. }
  24. }

源码分析

注意入栈的顺序和最后转置即可。

复杂度分析

同先序遍历。

Reference