Binary Tree Postorder Traversal
Link
Description
Given the root
of a binary tree, return the postorder traversal of its nodes' values.
Example 1:
- Input:
root = [1,null,2,3]
- Output:
[3,2,1]
Example 2:
- Input:
root = []
- Output:
[]
Example 3:
- Input:
root = [1]
- Output:
[1]
Constraints:
- The number of the nodes in the tree is in the range
[0, 100]
.
-100 <= Node.val <= 100
Solution
Recursive Method
| /**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
void postOrder(TreeNode* node, vector<int> &res) {
if (node == nullptr) return;
postOrder(node->left, res);
postOrder(node->right, res);
res.push_back(node->val);
}
vector<int> postorderTraversal(TreeNode* root) {
vector<int> res;
postOrder(root, res);
return res;
}
};
|
Iterative Method
Way 1
| /**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
stack<TreeNode*> stk;
vector<int> res;
if (root == nullptr) return res;
stk.push(root);
while (!stk.empty()) {
auto node = stk.top();
stk.pop();
res.push_back(node->val);
if (node->left) stk.push(node->left);
if (node->right) stk.push(node->right);
}
reverse(res.begin(), res.end());
return res;
}
};
|
Breakdown:
- Initialization and Base Case Check:
- The solution begins by initializing a stack (
stk
) and a result vector (res
).
- It checks if the
root
is nullptr
, returning an empty res
if true. This handles the edge case of an empty tree.
- Starting the Traversal:
- The
root
node is pushed onto the stack. This is the starting point of the traversal.
- Iterative Traversal:
- The traversal occurs inside a
while
loop, continuing as long as the stack is not empty.
- In each iteration, the top node of the stack is popped and its value is added to
res
.
- Then, if the left child of this node exists, it is pushed onto the stack, followed by the right child if it exists.
- Order of Pushing Children:
- The left child is pushed before the right child. This is crucial because stacks are LIFO (Last In, First Out) structures. By pushing the right child last, it ensures that the right child will be processed after the left child in subsequent iterations.
- Reverse the Result:
- After the traversal, the
res
vector, which is in root-right-left order, is reversed to achieve the left-right-root order of postorder traversal.
Way 2
| /**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
stack<TreeNode*> stk;
vector<int> res;
while (root || !stk.empty()) {
if (root) {
res.push_back(root->val);
stk.push(root);
root = root->right;
} else {
auto t = stk.top();
stk.pop();
root = t->left;
}
}
reverse(res.begin(), res.end());
return res;
}
};
|
Code with comments:
| class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
// A stack to hold TreeNode pointers during traversal.
stack<TreeNode*> stk;
// A vector to store the traversal result.
vector<int> res;
// Continue until all nodes are processed.
while (root || !stk.empty()) {
if (root) {
// Instead of processing the current node, push its value to the result.
// This is the reverse of the postorder.
res.push_back(root->val);
// Push the current node to the stack for later processing.
stk.push(root);
// Move to the right child, opposite of the usual left move in postorder.
root = root->right;
} else {
// Retrieve the most recently visited node.
auto t = stk.top();
stk.pop();
// Move to the left child.
root = t->left;
}
}
// Reverse the result to convert it into the correct postorder sequence.
reverse(res.begin(), res.end());
return res;
}
};
|
Steps of the Solution:
- Initialize a Stack and Result Vector: A stack and a result vector are initialized to manage the traversal and store the results, respectively.
- Iterative Traversal:
- If the current node (
root
) is not null, its value is added to the res
vector (mimicking postorder but in reverse).
- The current node is then pushed to the stack, and the traversal moves to the right child (not the left, as would be typical in postorder).
- If
root
is null, which indicates the end of a branch, the algorithm backtracks using the stack, retrieving the last visited node and moving to its left child.
- Reverse the Result: After the traversal, the
res
vector, which is in root-right-left order, is reversed to align with the left-right-root order of postorder traversal.
Time Complexity Analysis:
- \(O(n)\): Each node in the tree is visited exactly once, leading to a time complexity of \(O(n)\), where \(n\) is the number of nodes in the tree.
Space Complexity Analysis:
- Best Case (Balanced Tree): \(O(\log n)\), where the height of the tree is \(\log n\) and the stack's size will be proportional to the tree's height.
- Worst Case (Skewed Tree): \(O(n)\), where the tree is like a linked list, and the stack can potentially hold all the nodes.
- Average Case: Generally between \(O(\log n)\) and \(O(n)\), depending on the tree's structure.
Way 3
| /**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
vector<int> postOrder;
if (root == nullptr) return postOrder;
stack<TreeNode*> stk;
TreeNode *curr = root;
TreeNode *lastVisited = nullptr;
while (curr || ! stk.empty()) {
if (curr) {
stk.push(curr);
curr = curr->left;
} else {
TreeNode *peekNode = stk.top();
if (peekNode->right && peekNode->right != lastVisited) {
curr = peekNode->right;
} else {
stk.pop();
postOrder.push_back(peekNode->val);
lastVisited = peekNode;
}
}
}
return postOrder;
}
};
|
Code with comments:
| class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
// Vector to store the postorder traversal
vector<int> postOrder;
// If the root is null, return an empty vector
if (root == nullptr) return postOrder;
// Stack to hold the nodes during the traversal
stack<TreeNode*> stk;
// 'curr' will point to the current node being processed
TreeNode *curr = root;
// 'lastVisited' keeps track of the last visited node
TreeNode *lastVisited = nullptr;
// Continue until all nodes are processed
while (curr || !stk.empty()) {
if (curr) {
// If the current node is not null, push it onto the stack and go to its left child
stk.push(curr);
curr = curr->left;
} else {
// Peek at the node at the top of the stack without removing it
TreeNode *peekNode = stk.top();
// If the right child exists and hasn't been visited yet, go to the right child
if (peekNode->right && peekNode->right != lastVisited) {
curr = peekNode->right;
} else {
// If there is no right child or it's already visited, then process this node
stk.pop(); // Remove the node from the stack
postOrder.push_back(peekNode->val); // Add the node's value to the result vector
lastVisited = peekNode; // Mark this node as visited
}
}
}
return postOrder; // Return the postorder traversal
}
};
|
Explanation of the Algorithm:
- Initialization: The algorithm starts by initializing a vector
postOrder
to store the result of the postorder traversal. A stack stk
is used to keep track of nodes, and two pointers curr
and lastVisited
are initialized.
- Traversal Loop: The while loop continues as long as there are unprocessed nodes, indicated by either
curr
not being null or the stack not being empty.
- Left Subtree Processing: If
curr
is not null, it means we are going down the left subtree. The current node is pushed onto the stack, and we move curr
to its left child.
- Right Subtree and Node Processing: If
curr
is null, it means we have reached a leaf or are backtracking. We then peek at the top of the stack. If the right child of the peeked node exists and hasn't been visited, we set curr
to this right child. Otherwise, it's time to visit the peeked node: we pop it from the stack, add its value to postOrder
, and mark it as lastVisited
.
- Completion: Once all nodes are processed, the
postOrder
vector contains the elements in postorder traversal order.