Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

589. N-ary Tree Preorder Traversal - Leetcode Solution

Code Implementation

"""
# Definition for a Node.
class Node:
    def __init__(self, val=None, children=None):
        self.val = val
        self.children = children if children is not None else []

class Solution:
    def preorder(self, root: 'Node') -> List[int]:
        result = []
        def dfs(node):
            if not node:
                return
            result.append(node.val)
            for child in node.children:
                dfs(child)
        dfs(root)
        return result
/*
// Definition for a Node.
class Node {
public:
    int val;
    vector<Node*> children;

    Node() {}

    Node(int _val) {
        val = _val;
    }

    Node(int _val, vector<Node*> _children) {
        val = _val;
        children = _children;
    }
};
*/

class Solution {
public:
    vector<int> preorder(Node* root) {
        vector<int> result;
        dfs(root, result);
        return result;
    }
private:
    void dfs(Node* node, vector<int>& result) {
        if (!node) return;
        result.push_back(node->val);
        for (auto child : node->children) {
            dfs(child, result);
        }
    }
};
/*
// Definition for a Node.
class Node {
    public int val;
    public List<Node> children;

    public Node() {}

    public Node(int val) {
        this.val = val;
    }

    public Node(int val, List<Node> children) {
        this.val = val;
        this.children = children;
    }
};
*/

class Solution {
    public List<Integer> preorder(Node root) {
        List<Integer> result = new ArrayList<>();
        dfs(root, result);
        return result;
    }
    private void dfs(Node node, List<Integer> result) {
        if (node == null) return;
        result.add(node.val);
        for (Node child : node.children) {
            dfs(child, result);
        }
    }
}
/**
 * // Definition for a Node.
 * function Node(val, children) {
 *    this.val = val;
 *    this.children = children || [];
 * };
 */

/**
 * @param {Node|null} root
 * @return {number[]}
 */
var preorder = function(root) {
    const result = [];
    function dfs(node) {
        if (!node) return;
        result.push(node.val);
        for (const child of node.children) {
            dfs(child);
        }
    }
    dfs(root);
    return result;
};

Problem Description

You are given the root of an N-ary tree, where each node has a value and a list of children nodes. Your task is to return the preorder traversal of its nodes' values as a list.

  • In a preorder traversal, you visit the root node first, then recursively perform a preorder traversal of each child from left to right.
  • The tree can have any number of children at each node (not just two as in binary trees).
  • The input is a reference to the root node, and you must return a list of integers representing the node values in preorder.
  • Each node is defined as an object with a val (integer) and a children (list of Node objects).

Thought Process

To solve this problem, we need to understand how preorder traversal works for trees. In binary trees, preorder means: visit the root, then the left subtree, then the right subtree. In an N-ary tree, instead of just two children, each node can have zero or more children, and we must visit them in order.

The traversal is recursive in nature: for each node, process its value, then recursively process each of its children from left to right. A brute-force approach would be to manually manage which nodes to visit next, but recursion naturally fits this problem.

Alternatively, we could use an explicit stack to simulate recursion, but recursion is more straightforward here and matches the problem's recursive structure.

Solution Approach

  • Step 1: Create an empty list to store the traversal result.
  • Step 2: Define a helper function (often called dfs for "depth-first search") that takes a node as input.
  • Step 3: In the helper, if the node is null (or None), return immediately. This is the base case for recursion.
  • Step 4: Otherwise, add the node's value to the result list.
  • Step 5: Iterate over each child in the node's children list and recursively call the helper function on each child.
  • Step 6: Start the traversal by calling the helper function with the root node.
  • Step 7: Return the result list after the traversal completes.

We use recursion because it naturally expresses the "process the root, then each child" logic. The result list is built up as we visit each node in the correct order.

Example Walkthrough

Suppose the tree is:

        1
      / | \
     3  2  4
    / \
   5   6
  

The preorder traversal should return [1, 3, 5, 6, 2, 4].

  1. Start at root (1): add 1 to result ([1]).
  2. Visit first child (3): add 3 ([1, 3]).
  3. Visit 3's first child (5): add 5 ([1, 3, 5]).
  4. 5 has no children, backtrack to 3.
  5. Visit 3's second child (6): add 6 ([1, 3, 5, 6]).
  6. 6 has no children, backtrack to 3, then to 1.
  7. Visit 1's second child (2): add 2 ([1, 3, 5, 6, 2]).
  8. 2 has no children, backtrack to 1.
  9. Visit 1's third child (4): add 4 ([1, 3, 5, 6, 2, 4]).
  10. 4 has no children. Traversal complete.

At each step, we process the current node and then its children recursively, building the result in the correct order.

Time and Space Complexity

  • Brute-force approach: If we tried to flatten the tree by repeatedly appending children manually, we'd still visit every node once, so time complexity would still be O(N), where N is the total number of nodes.
  • Optimized recursive approach: Each node is visited exactly once, and each operation (adding to the result, iterating over children) is O(1) per node. Thus, the time complexity is O(N).
  • Space complexity: The result list takes O(N) space. The recursion stack can go as deep as the height of the tree, which in the worst case (a skewed tree) is O(N), but typically is less for balanced trees.

Summary

The N-ary Tree Preorder Traversal problem is a classic example of using recursion to traverse tree-like data structures. By processing the root node first and then recursively visiting each child, we can collect the nodes' values in the correct preorder sequence. The recursive solution is both elegant and efficient, requiring only a simple helper function and a result list. This approach leverages the natural recursive structure of trees and is optimal in both time and space for this problem.