Prerequisites
Before attempting this problem, you should be comfortable with:
- Binary Tree Traversal - Understanding left/right child relationships and how to build binary trees
- N-ary Tree Structure - Familiarity with trees where nodes can have multiple children stored in a list
- BFS (Breadth-First Search) - Using queues to process nodes level by level
- DFS (Depth-First Search) - Recursive tree traversal for encoding/decoding operations
- Left-Child Right-Sibling Representation - The key encoding technique where first child becomes left child and siblings chain as right children
1. BFS (Breadth-First Search) Traversal
Intuition
To encode an N-ary tree into a binary tree, we need a consistent rule for representing multiple children. The standard approach is the "left-child right-sibling" representation: the first child of an N-ary node becomes the left child of the binary node, and subsequent siblings are chained as right children. Using BFS, we process nodes level by level, building the binary tree structure while maintaining the parent-child relationships through a queue.
Algorithm
Encode:
- If the root is
null, returnnull. - Create a binary tree root with the same value.
- Use a queue storing pairs of (binary node, N-ary node).
- For each N-ary node's children:
- Link them as a chain: first child to
left, rest asrightsiblings. - Add each child pair to the queue.
- Link them as a chain: first child to
- Return the binary tree root.
Decode:
- If the root is
null, returnnull. - Create an N-ary root with the same value and an empty children list.
- Use a queue storing pairs of (N-ary node, binary node).
- For each binary node:
- Traverse the
leftchild'srightchain to reconstruct all children. - Add each child pair to the queue.
- Traverse the
- Return the N-ary tree root.
"""
# Definition for a Node.
class Node(object):
def __init__(self, val=None, children=None):
self.val = val
self.children = children
"""
"""
# Definition for a binary tree node.
class TreeNode(object):
def __init__(self, x):
self.val = x
self.left = None
self.right = None
"""
class Codec:
def encode(self, root):
"""Encodes an n-ary tree to a binary tree.
"""
if not root:
return None
rootNode = TreeNode(root.val)
queue = deque([(rootNode, root)])
while queue:
parent, curr = queue.popleft()
prevBNode = None
headBNode = None
# traverse each child one by one
for child in curr.children:
newBNode = TreeNode(child.val)
if prevBNode:
prevBNode.right = newBNode
else:
headBNode = newBNode
prevBNode = newBNode
queue.append((newBNode, child))
# use the first child in the left node of parent
parent.left = headBNode
return rootNode
def decode(self, data):
"""Decodes your binary tree to an n-ary tree.
"""
if not data:
return None
# should set the default value to [] rather than None,
# otherwise it wont pass the test cases.
rootNode = Node(data.val, [])
queue = deque([(rootNode, data)])
while queue:
parent, curr = queue.popleft()
firstChild = curr.left
sibling = firstChild
while sibling:
# Note: the initial value of the children list should not be None, which is assumed by the online judge.
newNode = Node(sibling.val, [])
parent.children.append(newNode)
queue.append((newNode, sibling))
sibling = sibling.right
return rootNodeTime & Space Complexity
Time complexity:
Space complexity:
encode(): .decode(): .Since is proportional to in the worst case, we could further generalize the space complexity to .
Where is the number of nodes in the N-ary tree, and is the maximum number of nodes that reside at the same level.
2. DFS (Depth-First Search) Traversal
Intuition
DFS offers a more elegant recursive solution using the same left-child right-sibling encoding. For each N-ary node, we recursively encode its first child and attach it as the left child of the binary node. The remaining children are encoded and linked as right siblings of the first child. Decoding reverses this: we traverse the left child's right chain to rebuild the children list.
Algorithm
Encode:
- If the root is
null, returnnull. - Create a binary node with the root's value.
- Recursively encode the first child and set it as
left. - For remaining children, recursively encode each and chain them as
rightsiblings. - Return the binary node.
Decode:
- If the root is
null, returnnull. - Create an N-ary node with the root's value and an empty children list.
- Start at the binary node's
leftchild. - While the current sibling exists:
- Recursively decode it and add to the children list.
- Move to the
rightsibling.
- Return the N-ary node.
"""
# Definition for a Node.
class Node(object):
def __init__(self, val=None, children=None):
self.val = val
self.children = children
"""
"""
# Definition for a binary tree node.
class TreeNode(object):
def __init__(self, x):
self.val = x
self.left = None
self.right = None
"""
class Codec:
def encode(self, root):
"""Encodes an n-ary tree to a binary tree.
"""
if not root:
return None
rootNode = TreeNode(root.val)
if len(root.children) > 0:
firstChild = root.children[0]
rootNode.left = self.encode(firstChild)
# the parent for the rest of the children
curr = rootNode.left
# encode the rest of the children
for i in range(1, len(root.children)):
curr.right = self.encode(root.children[i])
curr = curr.right
return rootNode
def decode(self, data):
"""Decodes your binary tree to an n-ary tree.
"""
if not data:
return None
rootNode = Node(data.val, [])
curr = data.left
while curr:
rootNode.children.append(self.decode(curr))
curr = curr.right
return rootNodeTime & Space Complexity
Time complexity:
Space complexity:
Since is proportional to in the worst case, we could further generalize the space complexity to
Unlike the BFS algorithm, we don't use the queue data structure in the DFS algorithm. However, implicitly the algorithm would consume more space in the function call stack due to the recursive function calls.
And this consumption of call stack space is the main space complexity for our DFS algorithm. As we can see, the size of the call stack at any moment is exactly the number of level where the currently visited node resides, e.g. for the root node (level 0), the recursive call stack is empty.
Where is the number of nodes in the N-ary tree, and is the depth of the N-ary tree.
Common Pitfalls
Confusing Left-Child and Right-Sibling Roles
In the encoding scheme, left points to the first child of the N-ary node, while right links siblings together. A common mistake is reversing these roles or using right for children, which breaks the bidirectional mapping and makes decoding impossible.
Not Initializing the Children List
When decoding, the N-ary node's children list must be initialized as an empty list, not null or None. Many online judges expect an empty list for leaf nodes, and failing to initialize causes null pointer exceptions when appending children.
Forgetting to Handle the Empty Tree Case
Both encode and decode must handle null input and return null. Forgetting this base case causes null pointer exceptions when the root is null, as the algorithm tries to access properties of a non-existent node.
Incorrect Sibling Chain Traversal During Decode
When decoding, you must traverse the entire right-sibling chain starting from the left child. A common mistake is only processing the immediate left child without following the right pointers, which loses all siblings after the first child.