BINARY TREE › N-ARY TREE
Open — the attempt gate is not wired up yet
This editorial is meant to unlock after you have run the problem at least once, with the worked solution behind one further deliberate click. That needs per-learner unlock state nothing stores today, so for now the whole article is open.
Try it yourself first →Postorder has one rule: a node is recorded only after all of its children have been recorded. For an n-ary node, apply that rule to every child from left to right. Each child is itself the root of another n-ary subtree, so the same instruction repeats until a leaf has no children left to visit.
The serialized input uses null as a boundary between child groups. After creating the root, process nodes in a queue. For each queued node, every value before the next null is one of its children, and each newly created child joins the queue so its own group can be parsed later. Once the tree exists, postorder is a direct depth-first traversal.
| MEASURE | BOUND | WHY |
|---|---|---|
| Time | O(n) | Parsing examines each serialized token once, creates each node once, and traversing follows each child edge once before recording each node. The parsing queue and traversal do not revisit a completed node, so the total work is linear in the tree size. |
| Space | O(n) extra | The returned vector is required output and is excluded. The token list, allocated tree, parsing queue, and recursion stack use O(n) extra space; the stack itself is O(h), which becomes O(n) for a chain-shaped tree, the worst possible shape. |
#include <queue>
#include <sstream>
#include <string>
#include <vector>
using namespace std;
struct Node {
int val;
vector<Node*> children;
Node(int value) : val(value) {}
};
class Solution {
public:
vector<int> postorder(string root) {
if (root == "null" || root == "[]") {
return {};
}
vector<string> tokens = splitTokens(root);
if (tokens.empty() || tokens[0] == "null") {
return {};
}
Node* rootNode = new Node(stoi(tokens[0]));
queue<Node*> pending;
pending.push(rootNode);
int index = 1;
while (!pending.empty() && index < static_cast<int>(tokens.size())) {
Node* current = pending.front();
pending.pop();
while (index < static_cast<int>(tokens.size()) &&
tokens[index] != "null") {
Node* child = new Node(stoi(tokens[index]));
current->children.push_back(child);
pending.push(child);
++index;
}
if (index < static_cast<int>(tokens.size()) &&
tokens[index] == "null") {
++index;
}
}
vector<int> result;
walk(rootNode, result);
return result;
}
private:
vector<string> splitTokens(const string& root) {
string inside = root.substr(1, root.size() - 2);
vector<string> tokens;
string token;
stringstream stream(inside);
while (getline(stream, token, ',')) {
size_t first = token.find_first_not_of(' ');
size_t last = token.find_last_not_of(' ');
if (first == string::npos) {
tokens.push_back("");
} else {
tokens.push_back(token.substr(first, last - first + 1));
}
}
return tokens;
}
void walk(Node* node, vector<int>& result) {
if (node == nullptr) {
return;
}
for (Node* child : node->children) {
walk(child, result);
}
result.push_back(node->val);
}
};The queue is used only while decoding the string. It separates the input format from the traversal logic: once every child pointer is attached, the traversal never needs to know where a null appeared. In walk, the loop must come before result.push_back. That placement is the entire postorder guarantee, while the null guard makes the same helper safe for every child call.
You can avoid recursion by first producing a root-right-to-left order with one stack, then reversing it. Push children from left to right; because a stack removes the last pushed child first, the next subtree processed is the rightmost one. Reversing the complete sequence restores left-to-right children with each parent after its children. This is an alternative arrangement, not an asymptotic optimisation: both versions use O(n) extra space overall.
#include <algorithm>
#include <queue>
#include <sstream>
#include <stack>
#include <string>
#include <vector>
using namespace std;
struct Node {
int val;
vector<Node*> children;
Node(int value) : val(value) {}
};
class Solution {
public:
vector<int> postorder(string root) {
if (root == "null" || root == "[]") {
return {};
}
vector<string> tokens = splitTokens(root);
if (tokens.empty() || tokens[0] == "null") {
return {};
}
Node* rootNode = new Node(stoi(tokens[0]));
queue<Node*> pending;
pending.push(rootNode);
int index = 1;
while (!pending.empty() && index < static_cast<int>(tokens.size())) {
Node* current = pending.front();
pending.pop();
while (index < static_cast<int>(tokens.size()) &&
tokens[index] != "null") {
Node* child = new Node(stoi(tokens[index]));
current->children.push_back(child);
pending.push(child);
++index;
}
if (index < static_cast<int>(tokens.size()) &&
tokens[index] == "null") {
++index;
}
}
vector<int> result;
stack<Node*> pendingTraversal;
pendingTraversal.push(rootNode);
while (!pendingTraversal.empty()) {
Node* node = pendingTraversal.top();
pendingTraversal.pop();
result.push_back(node->val);
for (Node* child : node->children) {
pendingTraversal.push(child);
}
}
reverse(result.begin(), result.end());
return result;
}
private:
vector<string> splitTokens(const string& root) {
string inside = root.substr(1, root.size() - 2);
vector<string> tokens;
string token;
stringstream stream(inside);
while (getline(stream, token, ',')) {
size_t first = token.find_first_not_of(' ');
size_t last = token.find_last_not_of(' ');
if (first == string::npos) {
tokens.push_back("");
} else {
tokens.push_back(token.substr(first, last - first + 1));
}
}
return tokens;
}
};