Facebook Pixel

2649. Nested Array Generator

Problem Description

This problem asks you to create a generator function that traverses a multi-dimensional array and yields all integers in the order they appear when reading from left to right.

A multi-dimensional array is a nested structure that can contain:

  • Regular integers
  • Other arrays (which can themselves contain integers or more arrays)

The task is to implement an inorderTraversal generator function that:

  1. Takes a multi-dimensional array as input
  2. Iterates through each element from left to right
  3. When it encounters an integer, yields that integer
  4. When it encounters an array, recursively applies the same traversal logic to that array
  5. Returns all integers in the exact order they would be encountered during this left-to-right traversal

For example, given the array [1, [2, 3]]:

  • First element is 1 (an integer), so yield 1
  • Second element is [2, 3] (an array), so traverse it:
    • First element is 2, yield 2
    • Second element is 3, yield 3
  • Final output sequence: 1, 2, 3

The solution uses a generator function with yield to produce values one at a time. When encountering a nested array, it uses yield* to delegate to a recursive call of the same generator function, effectively flattening the nested structure while maintaining the left-to-right order.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

When we need to process a nested structure and extract all values in order, the natural approach is to think recursively. Each element in the array can be one of two things: either a simple integer that we want to output, or another array that needs the same processing logic applied to it.

The key insight is recognizing that this is a tree-like structure where:

  • Integers are leaf nodes that should be yielded immediately
  • Arrays are internal nodes that contain children requiring further traversal

Since we want to process elements from left to right and maintain the order, we need to fully process each element before moving to the next. This naturally leads to a depth-first approach - when we encounter a nested array, we must completely traverse it before continuing with the next sibling element.

Generators are perfect for this problem because:

  1. We don't know how many total integers exist until we traverse the entire structure
  2. We want to produce values lazily, one at a time, rather than collecting everything in memory first
  3. The yield* syntax elegantly handles delegation to recursive calls

The pattern becomes clear: iterate through each element, check its type, and either yield it directly (if it's a number) or delegate to a recursive call (if it's an array). The yield* operator is particularly elegant here as it automatically yields all values from the recursive generator call, maintaining the correct order without manual iteration.

This approach mirrors how we would manually flatten such a structure - process each item in order, and when we find a nested array, we pause our current level, fully process the nested array, then resume where we left off.

Solution Approach

The implementation uses a recursive generator function with type checking to traverse the multi-dimensional array structure.

Implementation Details:

  1. Type Definition: First, we define MultidimensionalArray as a recursive type that can contain either numbers or other MultidimensionalArray instances:

    type MultidimensionalArray = (MultidimensionalArray | number)[];
  2. Generator Function Declaration: We declare inorderTraversal as a generator function using function* syntax, which returns a Generator<number, void, unknown>:

    • The first type parameter number indicates the yielded values are numbers
    • The second parameter void indicates no return value when complete
    • The third parameter unknown is for the next() method's argument type
  3. Main Traversal Loop: We iterate through each element in the input array using a for...of loop:

    for (const e of arr) {
  4. Type Checking and Processing: For each element e, we check if it's an array or a number:

    • If it's an array (Array.isArray(e)): We recursively call inorderTraversal(e) and use yield* to delegate to that generator. The yield* expression automatically iterates through all values produced by the recursive call and yields them in sequence.
    yield* inorderTraversal(e);
    • If it's a number (else case): We directly yield the number value.
    yield e;

Why This Works:

  • The recursion naturally handles arbitrary nesting depth
  • yield* flattens the recursive generator calls, ensuring all nested values are yielded in the correct order
  • The left-to-right iteration of the for...of loop preserves the inorder traversal requirement
  • No additional data structures are needed - the call stack handles the recursion, and the generator manages the output sequence

Time Complexity: O(n) where n is the total number of elements (both arrays and integers) in the structure, as we visit each element exactly once.

Space Complexity: O(d) where d is the maximum depth of nesting, representing the recursion stack depth.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through the solution with the example arr = [[1, 2], 3, [4, [5, 6]]].

Initial Call: inorderTraversal([[1, 2], 3, [4, [5, 6]]])

Step 1: First element is [1, 2] (an array)

  • Check: Array.isArray([1, 2]) → true

  • Execute: yield* inorderTraversal([1, 2])

  • This creates a recursive call

    Recursive Call 1: inorderTraversal([1, 2])

    • Element 1 (number) → yield 1
    • Element 2 (number) → yield 2
    • Recursive call complete

Step 2: Second element is 3 (a number)

  • Check: Array.isArray(3) → false
  • Execute: yield 3

Step 3: Third element is [4, [5, 6]] (an array)

  • Check: Array.isArray([4, [5, 6]]) → true

  • Execute: yield* inorderTraversal([4, [5, 6]])

  • This creates another recursive call

    Recursive Call 2: inorderTraversal([4, [5, 6]])

    • Element 4 (number) → yield 4

    • Element [5, 6] (array) → yield* inorderTraversal([5, 6])

      Recursive Call 3: inorderTraversal([5, 6])

      • Element 5 (number) → yield 5
      • Element 6 (number) → yield 6
      • Recursive call complete
    • Recursive call 2 complete

Final Output Sequence: 1, 2, 3, 4, 5, 6

The key insight is how yield* seamlessly delegates to recursive calls, automatically yielding all values from nested arrays in the correct order. Each time we encounter an array, we pause the current iteration, fully process that array (and any arrays it contains), then resume where we left off.

Solution Implementation

1from typing import Generator, List, Union
2
3# Type definition for a multidimensional array that can contain numbers or nested arrays
4MultidimensionalArray = List[Union[int, 'MultidimensionalArray']]
5
6def inorderTraversal(arr: MultidimensionalArray) -> Generator[int, None, None]:
7    """
8    Generator function that performs an inorder traversal of a multidimensional array
9    Flattens the array by yielding all numbers in the order they appear (depth-first)
10  
11    Args:
12        arr: The multidimensional array to traverse
13      
14    Yields:
15        int: Each number found in the array structure
16    """
17    # Iterate through each element in the current array
18    for element in arr:
19        # Check if the current element is a list (nested array)
20        if isinstance(element, list):
21            # Recursively traverse nested array and yield all its values
22            yield from inorderTraversal(element)
23        else:
24            # Element is a number, yield it directly
25            yield element
26
27
28# Example usage:
29# gen = inorderTraversal([1, [2, 3]])
30# next(gen)  # 1
31# next(gen)  # 2
32# next(gen)  # 3
33
1import java.util.*;
2
3/**
4 * Class to handle inorder traversal of a multidimensional array structure
5 */
6public class MultidimensionalArrayTraversal {
7  
8    /**
9     * Generator-like class that performs an inorder traversal of a multidimensional array
10     * Flattens the array by yielding all numbers in the order they appear (depth-first)
11     */
12    public static class InorderTraversalIterator implements Iterator<Integer> {
13        private Stack<Iterator<?>> iteratorStack;
14        private Integer nextValue;
15      
16        /**
17         * Constructor that initializes the iterator with the given array
18         * @param arr - The multidimensional array to traverse (can contain Integer or List objects)
19         */
20        public InorderTraversalIterator(List<?> arr) {
21            this.iteratorStack = new Stack<>();
22            this.iteratorStack.push(arr.iterator());
23            this.nextValue = null;
24            advance();
25        }
26      
27        /**
28         * Advances to the next integer value in the structure
29         */
30        private void advance() {
31            nextValue = null;
32          
33            while (!iteratorStack.isEmpty()) {
34                Iterator<?> currentIterator = iteratorStack.peek();
35              
36                // Check if current iterator has more elements
37                if (!currentIterator.hasNext()) {
38                    iteratorStack.pop();
39                    continue;
40                }
41              
42                Object element = currentIterator.next();
43              
44                // Check if the current element is a List (nested array)
45                if (element instanceof List) {
46                    // Push the nested list's iterator onto the stack for recursive traversal
47                    iteratorStack.push(((List<?>) element).iterator());
48                } else if (element instanceof Integer) {
49                    // Element is a number, store it as next value
50                    nextValue = (Integer) element;
51                    return;
52                }
53            }
54        }
55      
56        @Override
57        public boolean hasNext() {
58            return nextValue != null;
59        }
60      
61        @Override
62        public Integer next() {
63            if (!hasNext()) {
64                throw new NoSuchElementException();
65            }
66            Integer result = nextValue;
67            advance();
68            return result;
69        }
70    }
71  
72    /**
73     * Creates an iterator for inorder traversal of a multidimensional array
74     * @param arr - The multidimensional array to traverse
75     * @return Iterator that yields each number found in the array structure
76     */
77    public static Iterator<Integer> inorderTraversal(List<?> arr) {
78        return new InorderTraversalIterator(arr);
79    }
80  
81    /**
82     * Example usage:
83     * List<?> arr = Arrays.asList(1, Arrays.asList(2, 3));
84     * Iterator<Integer> iter = inorderTraversal(arr);
85     * iter.next(); // 1
86     * iter.next(); // 2
87     * iter.next(); // 3
88     */
89}
90
1#include <vector>
2#include <variant>
3#include <memory>
4#include <coroutine>
5#include <exception>
6
7// Type definition for a multidimensional array that can contain numbers or nested arrays
8// Using variant to represent either a number or a nested array
9class MultidimensionalArray;
10using ArrayElement = std::variant<int, std::shared_ptr<MultidimensionalArray>>;
11
12class MultidimensionalArray : public std::vector<ArrayElement> {
13public:
14    using std::vector<ArrayElement>::vector;
15};
16
17// Generator template for coroutine support
18template<typename T>
19struct Generator {
20    struct promise_type {
21        T current_value;
22        std::exception_ptr exception;
23      
24        Generator get_return_object() {
25            return Generator{std::coroutine_handle<promise_type>::from_promise(*this)};
26        }
27      
28        std::suspend_always initial_suspend() { return {}; }
29        std::suspend_always final_suspend() noexcept { return {}; }
30      
31        void unhandled_exception() {
32            exception = std::current_exception();
33        }
34      
35        template<std::convertible_to<T> From>
36        std::suspend_always yield_value(From&& from) {
37            current_value = std::forward<From>(from);
38            return {};
39        }
40      
41        void return_void() {}
42    };
43  
44    std::coroutine_handle<promise_type> handle;
45  
46    Generator(std::coroutine_handle<promise_type> h) : handle(h) {}
47    ~Generator() {
48        if (handle)
49            handle.destroy();
50    }
51  
52    // Move constructor and assignment
53    Generator(Generator&& other) noexcept : handle(std::exchange(other.handle, {})) {}
54    Generator& operator=(Generator&& other) noexcept {
55        if (this != &other) {
56            if (handle)
57                handle.destroy();
58            handle = std::exchange(other.handle, {});
59        }
60        return *this;
61    }
62  
63    // Delete copy operations
64    Generator(const Generator&) = delete;
65    Generator& operator=(const Generator&) = delete;
66  
67    // Get the next value
68    bool next() {
69        handle.resume();
70        return !handle.done();
71    }
72  
73    // Get current value
74    T value() const {
75        return handle.promise().current_value;
76    }
77};
78
79/**
80 * Generator function that performs an inorder traversal of a multidimensional array
81 * Flattens the array by yielding all numbers in the order they appear (depth-first)
82 * 
83 * @param arr - The multidimensional array to traverse
84 * @yields int - Each number found in the array structure
85 */
86Generator<int> inorderTraversal(const MultidimensionalArray& arr) {
87    // Iterate through each element in the current array
88    for (const auto& element : arr) {
89        // Check if the current element is a nested array
90        if (std::holds_alternative<std::shared_ptr<MultidimensionalArray>>(element)) {
91            // Get the nested array pointer
92            auto nested_array = std::get<std::shared_ptr<MultidimensionalArray>>(element);
93            // Recursively traverse nested array and yield all its values
94            auto nested_generator = inorderTraversal(*nested_array);
95            while (nested_generator.next()) {
96                co_yield nested_generator.value();
97            }
98        } else {
99            // Element is a number, yield it directly
100            co_yield std::get<int>(element);
101        }
102    }
103}
104
105/**
106 * Example usage:
107 * auto arr = MultidimensionalArray{
108 *     1,
109 *     std::make_shared<MultidimensionalArray>(MultidimensionalArray{2, 3})
110 * };
111 * auto gen = inorderTraversal(arr);
112 * gen.next(); gen.value(); // 1
113 * gen.next(); gen.value(); // 2
114 * gen.next(); gen.value(); // 3
115 */
116
1// Type definition for a multidimensional array that can contain numbers or nested arrays
2type MultidimensionalArray = (MultidimensionalArray | number)[];
3
4/**
5 * Generator function that performs an inorder traversal of a multidimensional array
6 * Flattens the array by yielding all numbers in the order they appear (depth-first)
7 * 
8 * @param arr - The multidimensional array to traverse
9 * @yields {number} - Each number found in the array structure
10 */
11function* inorderTraversal(arr: MultidimensionalArray): Generator<number, void, unknown> {
12    // Iterate through each element in the current array
13    for (const element of arr) {
14        // Check if the current element is an array
15        if (Array.isArray(element)) {
16            // Recursively traverse nested array and yield all its values
17            yield* inorderTraversal(element);
18        } else {
19            // Element is a number, yield it directly
20            yield element;
21        }
22    }
23}
24
25/**
26 * Example usage:
27 * const gen = inorderTraversal([1, [2, 3]]);
28 * gen.next().value; // 1
29 * gen.next().value; // 2
30 * gen.next().value; // 3
31 */
32

Time and Space Complexity

Time Complexity: O(n) where n is the total number of elements in the multidimensional array (including all nested elements).

The generator function visits each element exactly once. For every element in the array structure:

  • If it's a number, it yields the value in O(1) time
  • If it's an array, it recursively processes that array
  • The yield* delegation doesn't add extra overhead per element

Since each element is processed exactly once regardless of nesting depth, the total time complexity is linear with respect to the total number of elements.

Space Complexity: O(d) where d is the maximum depth of nesting in the multidimensional array.

The space complexity comes from the recursive call stack:

  • Each recursive call to inorderTraversal adds a new frame to the call stack
  • The maximum number of frames on the stack at any time equals the maximum nesting depth
  • The generator itself maintains minimal state (just the current position in iteration)
  • No additional data structures are used to store elements

For example:

  • [1, 2, 3] has depth 1, so space complexity is O(1)
  • [1, [2, [3, [4]]]] has depth 4, so space complexity is O(4)

In the worst case where the array is deeply nested (like [[[...n levels...[1]...]]]), the space complexity would be O(n), but typically d << n for most practical inputs.

Common Pitfalls

1. Type Confusion with Mixed or Invalid Input Types

A common pitfall is assuming the input will always contain only integers and arrays. In real-world scenarios, the array might contain other types like floats, strings, None values, or even objects, which could cause unexpected behavior or errors.

Problem Example:

# This could cause issues with the original implementation
arr = [1, 2.5, "3", None, [4, 5]]

Solution: Add robust type checking and handle different numeric types:

def inorderTraversal(arr: MultidimensionalArray) -> Generator[int, None, None]:
    for element in arr:
        if isinstance(element, list):
            yield from inorderTraversal(element)
        elif isinstance(element, (int, float)):  # Handle both int and float
            # Convert float to int if needed, or yield as-is
            yield int(element)  # or keep as float based on requirements
        # Silently skip non-numeric, non-list elements

2. Circular References Leading to Infinite Recursion

If the data structure contains circular references (an array that references itself directly or indirectly), the recursive approach will cause a stack overflow.

Problem Example:

arr = [1, 2]
arr.append(arr)  # Creates circular reference: [1, 2, [...]]
# Calling inorderTraversal(arr) would cause infinite recursion

Solution: Track visited arrays to detect cycles:

def inorderTraversal(arr: MultidimensionalArray, visited=None) -> Generator[int, None, None]:
    if visited is None:
        visited = set()
  
    # Get unique identifier for this array
    arr_id = id(arr)
  
    # Check for circular reference
    if arr_id in visited:
        return  # Stop recursion if we've seen this array before
  
    visited.add(arr_id)
  
    for element in arr:
        if isinstance(element, list):
            yield from inorderTraversal(element, visited)
        else:
            yield element
  
    visited.remove(arr_id)  # Clean up after processing

3. Memory Issues with Very Deep Nesting

Python has a default recursion limit (typically 1000), and deeply nested arrays can cause a RecursionError.

Problem Example:

# Create a deeply nested array
deep_array = 1
for _ in range(2000):
    deep_array = [deep_array]
# This would exceed Python's recursion limit

Solution: Use an iterative approach with an explicit stack:

def inorderTraversal(arr: MultidimensionalArray) -> Generator[int, None, None]:
    stack = [iter(arr)]
  
    while stack:
        try:
            element = next(stack[-1])
            if isinstance(element, list):
                # Push new iterator for nested array
                stack.append(iter(element))
            else:
                yield element
        except StopIteration:
            # Current level exhausted, go back up
            stack.pop()

4. Modification During Iteration

Modifying the array structure while the generator is active can lead to unexpected behavior or errors.

Problem Example:

arr = [1, [2, 3], 4]
gen = inorderTraversal(arr)
next(gen)  # yields 1
arr[1].append(5)  # Modifying during iteration
# Subsequent calls might miss or include the new element unexpectedly

Solution: Either document this limitation clearly or create a defensive copy:

def inorderTraversal(arr: MultidimensionalArray) -> Generator[int, None, None]:
    # Create a deep copy to prevent modification issues
    import copy
    arr_copy = copy.deepcopy(arr)
  
    for element in arr_copy:
        if isinstance(element, list):
            yield from inorderTraversal(element)
        else:
            yield element

Note that creating a deep copy has performance implications and should be used judiciously based on requirements.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of the following uses divide and conquer strategy?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More