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:
- Takes a multi-dimensional array as input
- Iterates through each element from left to right
- When it encounters an integer, yields that integer
- When it encounters an array, recursively applies the same traversal logic to that array
- 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 yield1
- Second element is
[2, 3]
(an array), so traverse it:- First element is
2
, yield2
- Second element is
3
, yield3
- First element is
- 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.
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:
- We don't know how many total integers exist until we traverse the entire structure
- We want to produce values lazily, one at a time, rather than collecting everything in memory first
- 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:
-
Type Definition: First, we define
MultidimensionalArray
as a recursive type that can contain either numbers or otherMultidimensionalArray
instances:type MultidimensionalArray = (MultidimensionalArray | number)[];
-
Generator Function Declaration: We declare
inorderTraversal
as a generator function usingfunction*
syntax, which returns aGenerator<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
- The first type parameter
-
Main Traversal Loop: We iterate through each element in the input array using a
for...of
loop:for (const e of arr) {
-
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 callinorderTraversal(e)
and useyield*
to delegate to that generator. Theyield*
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;
- If it's an array (
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 EvaluatorExample 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
- Element
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
- Element
-
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 isO(1)
[1, [2, [3, [4]]]]
has depth 4, so space complexity isO(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.
Which of the following uses divide and conquer strategy?
Recommended Readings
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!