Facebook Pixel

341. Flatten Nested List Iterator

Problem Description

You need to implement an iterator that can traverse through a nested list structure where each element can be either:

  • An integer value
  • A list that contains more integers or nested lists

The nested list is represented using a NestedInteger interface that provides three methods:

  • isInteger(): Returns True if the element holds a single integer, False if it holds a nested list
  • getInteger(): Returns the integer value if the element is an integer
  • getList(): Returns the nested list if the element is a list

Your task is to implement the NestedIterator class with three methods:

  1. __init__(nestedList): Constructor that takes the nested list and initializes the iterator
  2. next(): Returns the next integer in the flattened sequence
  3. hasNext(): Returns True if there are more integers to iterate through, False otherwise

The iterator should flatten the nested structure and return integers one by one in the order they appear when traversing the nested list from left to right.

For example, given [[1,1],2,[1,1]], the iterator should return the integers in order: 1, 1, 2, 1, 1.

The solution approach uses a depth-first search (DFS) during initialization to flatten the entire nested list into a simple list of integers. It recursively traverses each element:

  • If an element is an integer, it adds it directly to the flattened list
  • If an element is a nested list, it recursively processes that list

The iterator then maintains an index pointer i to track the current position in the flattened list. The next() method increments this pointer and returns the current element, while hasNext() checks if there are more elements remaining by comparing the next position with the list length.

Flowchart Walkthrough

First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:

Is it a graph?

  • Yes: The nested list structure can be viewed as a graph where each list is a node that can contain other nodes (nested lists) or leaf values (integers). This creates a tree-like hierarchical structure.

Is it a tree?

  • Yes: The nested list structure is indeed a tree. Each nested list can be seen as a node with children (either integers or other nested lists). There's a root (the main list), and we need to traverse all nodes to flatten the structure. There are no cycles - each element belongs to exactly one parent list.

DFS

  • Yes: Since we identified this as a tree traversal problem, DFS is the appropriate algorithm. We need to visit each node (nested list) and recursively explore its children to extract all integers in the correct order.

Conclusion: The flowchart suggests using DFS (Depth-First Search) for flattening the nested list iterator. This makes perfect sense because:

  1. We need to traverse a tree-like structure (nested lists)
  2. We must visit every element to extract all integers
  3. The order matters (left-to-right traversal)
  4. DFS naturally handles the recursive nature of nested structures by going deep into each nested list before moving to the next sibling

The solution implements DFS in the constructor to pre-flatten the entire structure into a simple list, making the iterator operations (next() and hasNext()) very efficient with O(1) time complexity.

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

Intuition

When we look at this nested list structure, we're essentially dealing with a tree where each node can either be a leaf (integer) or a branch (nested list). The challenge is to visit every integer in the correct order, just like reading a book - we read each word sequentially, even though the book has a hierarchical structure of chapters, sections, and paragraphs.

The key insight is recognizing that we need to "flatten" this hierarchical structure into a linear sequence. Think of it like unpacking nested boxes - we open the main box, and for each item inside, if it's another box, we open that too, continuing until we find all the actual items (integers).

Since we need to access elements one by one through next() calls, we have two main strategies:

  1. Lazy evaluation: Only flatten elements when needed during next() or hasNext() calls
  2. Eager evaluation: Flatten everything upfront during initialization

The eager approach is simpler and more intuitive here. Why? Because:

  • We're going to visit all elements anyway (the iterator will traverse the entire structure)
  • Pre-flattening makes next() and hasNext() operations trivial - just array indexing
  • We avoid the complexity of maintaining state about partially explored nested lists

The DFS pattern naturally fits this problem because it mirrors how we'd manually flatten the list: when we encounter a nested list, we immediately dive into it, process all its contents, then continue with the next element at the current level. This is exactly what DFS does - it goes as deep as possible before backtracking.

The recursive nature of DFS perfectly matches the recursive structure of nested lists. Each recursive call handles one level of nesting, and the base case (finding an integer) is where we actually collect the values into our flattened result.

Solution Approach

The solution implements an eager flattening strategy using DFS during the initialization phase. Let's walk through the implementation step by step:

1. Data Structure Setup

The NestedIterator class maintains two instance variables:

  • self.nums: A flat list that will store all integers after flattening
  • self.i: An index pointer initialized to -1 to track the current position in the iteration

2. DFS Flattening Algorithm

The core of the solution is the nested dfs function defined inside __init__:

def dfs(ls):
    for x in ls:
        if x.isInteger():
            self.nums.append(x.getInteger())
        else:
            dfs(x.getList())

This recursive function:

  • Iterates through each element x in the current list ls
  • For each element, checks if it's an integer using x.isInteger()
  • If it's an integer: extracts the value with x.getInteger() and appends it to self.nums
  • If it's a nested list: recursively calls dfs with x.getList() to process the nested structure

3. Initialization Process

def __init__(self, nestedList: [NestedInteger]):
    self.nums = []
    self.i = -1
    dfs(nestedList)

During initialization:

  • Create an empty list self.nums to store the flattened integers
  • Set the index pointer self.i to -1 (before the first element)
  • Call dfs(nestedList) to flatten the entire structure upfront

4. Iterator Operations

Once the list is flattened, the iterator operations become trivial:

def next(self) -> int:
    self.i += 1
    return self.nums[self.i]
  • Increment the index pointer
  • Return the integer at the current position
def hasNext(self) -> bool:
    return self.i + 1 < len(self.nums)
  • Check if the next position (self.i + 1) is within bounds of the flattened list

Time and Space Complexity:

  • Initialization: O(N) time where N is the total number of integers, as we visit each element once
  • next(): O(1) time - simple array access
  • hasNext(): O(1) time - simple comparison
  • Space: O(N) to store all integers in the flattened list, plus O(D) for the recursion stack where D is the maximum depth of nesting

This approach trades upfront computation cost for extremely efficient iteration operations, which is ideal when we expect to iterate through all elements.

Ready to land your dream job?

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

Start Evaluator

Example Walkthrough

Let's walk through the solution with the example [[1,1],2,[1,1]].

Initial Structure Visualization:

[
  [1, 1],      // Element 0: nested list
  2,           // Element 1: integer
  [1, 1]       // Element 2: nested list
]

Step 1: Initialization

  • Create empty nums = [] and set i = -1
  • Call dfs([[1,1],2,[1,1]])

Step 2: DFS Traversal

Processing element 0: [1,1]

  • isInteger() returns False → it's a list
  • Recursively call dfs([1,1])
    • Process first 1: isInteger() returns True → append to nums = [1]
    • Process second 1: isInteger() returns True → append to nums = [1,1]

Processing element 1: 2

  • isInteger() returns True → append to nums = [1,1,2]

Processing element 2: [1,1]

  • isInteger() returns False → it's a list
  • Recursively call dfs([1,1])
    • Process first 1: isInteger() returns True → append to nums = [1,1,2,1]
    • Process second 1: isInteger() returns True → append to nums = [1,1,2,1,1]

Step 3: Final State After Initialization

  • nums = [1,1,2,1,1] (fully flattened)
  • i = -1 (ready to start iteration)

Step 4: Using the Iterator

OperationActioni valueReturn valuenums[i]
hasNext()Check if i+1 < 50 < 5-1True-
next()Increment i, return nums[0]011
hasNext()Check if i+1 < 51 < 50True-
next()Increment i, return nums[1]111
next()Increment i, return nums[2]222
next()Increment i, return nums[3]311
next()Increment i, return nums[4]411
hasNext()Check if i+1 < 55 < 54False-

The iterator successfully returns all integers in order: 1, 1, 2, 1, 1.

Solution Implementation

1# """
2# This is the interface that allows for creating nested lists.
3# You should not implement it, or speculate about its implementation
4# """
5# class NestedInteger:
6#    def isInteger(self) -> bool:
7#        """
8#        @return True if this NestedInteger holds a single integer, rather than a nested list.
9#        """
10#
11#    def getInteger(self) -> int:
12#        """
13#        @return the single integer that this NestedInteger holds, if it holds a single integer
14#        Return None if this NestedInteger holds a nested list
15#        """
16#
17#    def getList(self) -> [NestedInteger]:
18#        """
19#        @return the nested list that this NestedInteger holds, if it holds a nested list
20#        Return None if this NestedInteger holds a single integer
21#        """
22
23
24class NestedIterator:
25    def __init__(self, nestedList: list[NestedInteger]) -> None:
26        """
27        Initialize the iterator with a nested list.
28        Flatten the nested structure into a simple list of integers.
29      
30        Args:
31            nestedList: A list of NestedInteger objects that may contain integers or nested lists
32        """
33        def flatten_nested_list(nested_list: list[NestedInteger]) -> None:
34            """
35            Recursively flatten the nested list structure using depth-first search.
36          
37            Args:
38                nested_list: A list of NestedInteger objects to flatten
39            """
40            for nested_integer in nested_list:
41                if nested_integer.isInteger():
42                    # If it's a single integer, add it to the flattened list
43                    self.flattened_list.append(nested_integer.getInteger())
44                else:
45                    # If it's a nested list, recursively flatten it
46                    flatten_nested_list(nested_integer.getList())
47      
48        # Initialize the flattened list to store all integers
49        self.flattened_list: list[int] = []
50        # Initialize the current index pointer (starts at -1, will be incremented before first access)
51        self.current_index: int = -1
52      
53        # Flatten the entire nested list structure
54        flatten_nested_list(nestedList)
55  
56    def next(self) -> int:
57        """
58        Return the next integer in the iteration.
59      
60        Returns:
61            The next integer in the flattened list
62        """
63        # Move to the next position
64        self.current_index += 1
65        # Return the integer at the current position
66        return self.flattened_list[self.current_index]
67  
68    def hasNext(self) -> bool:
69        """
70        Check if there are more integers to iterate over.
71      
72        Returns:
73            True if there are more integers, False otherwise
74        """
75        # Check if the next position is within bounds
76        return self.current_index + 1 < len(self.flattened_list)
77
78
79# Your NestedIterator object will be instantiated and called as such:
80# i, v = NestedIterator(nestedList), []
81# while i.hasNext(): v.append(i.next())
82
1/**
2 * // This is the interface that allows for creating nested lists.
3 * // You should not implement it, or speculate about its implementation
4 * public interface NestedInteger {
5 *
6 *     // @return true if this NestedInteger holds a single integer, rather than a nested list.
7 *     public boolean isInteger();
8 *
9 *     // @return the single integer that this NestedInteger holds, if it holds a single integer
10 *     // Return null if this NestedInteger holds a nested list
11 *     public Integer getInteger();
12 *
13 *     // @return the nested list that this NestedInteger holds, if it holds a nested list
14 *     // Return empty list if this NestedInteger holds a single integer
15 *     public List<NestedInteger> getList();
16 * }
17 */
18
19/**
20 * Iterator for flattening nested lists of integers.
21 * This class implements the Iterator interface to provide sequential access
22 * to all integers in a nested list structure.
23 */
24public class NestedIterator implements Iterator<Integer> {
25    // List to store all flattened integers from the nested structure
26    private List<Integer> flattenedList;
27  
28    // Current index position in the flattened list
29    private int currentIndex;
30
31    /**
32     * Constructor that initializes the iterator with a nested list.
33     * Flattens the entire nested structure during initialization.
34     * 
35     * @param nestedList the nested list structure to iterate over
36     */
37    public NestedIterator(List<NestedInteger> nestedList) {
38        this.flattenedList = new ArrayList<>();
39        this.currentIndex = -1;
40      
41        // Flatten the nested list structure using depth-first search
42        flatten(nestedList);
43    }
44
45    /**
46     * Returns the next integer in the iteration.
47     * 
48     * @return the next integer element
49     */
50    @Override
51    public Integer next() {
52        // Increment index and return the element at that position
53        currentIndex++;
54        return flattenedList.get(currentIndex);
55    }
56
57    /**
58     * Checks if there are more integers to iterate over.
59     * 
60     * @return true if there are more elements, false otherwise
61     */
62    @Override
63    public boolean hasNext() {
64        // Check if the next index is within bounds
65        return currentIndex + 1 < flattenedList.size();
66    }
67
68    /**
69     * Recursively flattens a nested list structure using depth-first search.
70     * Traverses through each element, adding integers directly to the flattened list
71     * and recursively processing nested lists.
72     * 
73     * @param nestedList the nested list to flatten
74     */
75    private void flatten(List<NestedInteger> nestedList) {
76        for (NestedInteger element : nestedList) {
77            if (element.isInteger()) {
78                // If element is an integer, add it to the flattened list
79                flattenedList.add(element.getInteger());
80            } else {
81                // If element is a nested list, recursively flatten it
82                flatten(element.getList());
83            }
84        }
85    }
86}
87
88/**
89 * Your NestedIterator object will be instantiated and called as such:
90 * NestedIterator i = new NestedIterator(nestedList);
91 * while (i.hasNext()) v[f()] = i.next();
92 */
93
1/**
2 * // This is the interface that allows for creating nested lists.
3 * // You should not implement it, or speculate about its implementation
4 * class NestedInteger {
5 *   public:
6 *     // Return true if this NestedInteger holds a single integer, rather than a nested list.
7 *     bool isInteger() const;
8 *
9 *     // Return the single integer that this NestedInteger holds, if it holds a single integer
10 *     // The result is undefined if this NestedInteger holds a nested list
11 *     int getInteger() const;
12 *
13 *     // Return the nested list that this NestedInteger holds, if it holds a nested list
14 *     // The result is undefined if this NestedInteger holds a single integer
15 *     const vector<NestedInteger> &getList() const;
16 * };
17 */
18
19class NestedIterator {
20public:
21    /**
22     * Constructor initializes the iterator with a nested list
23     * Flattens the nested structure into a single vector of integers
24     * @param nestedList - Input nested list structure to iterate over
25     */
26    NestedIterator(vector<NestedInteger>& nestedList) {
27        // Define recursive lambda function to flatten the nested list
28        function<void(const vector<NestedInteger>&)> flatten = [&](const vector<NestedInteger>& list) -> void {
29            // Iterate through each element in the current list
30            for (const auto& element : list) {
31                if (element.isInteger()) {
32                    // If element is an integer, add it to the flattened vector
33                    flattenedList.push_back(element.getInteger());
34                } else {
35                    // If element is a nested list, recursively flatten it
36                    flatten(element.getList());
37                }
38            }
39        };
40      
41        // Start the flattening process from the root nested list
42        flatten(nestedList);
43    }
44  
45    /**
46     * Returns the next integer in the iteration
47     * @return The next integer value
48     */
49    int next() {
50        // Increment current index and return the value at that position
51        return flattenedList[++currentIndex];
52    }
53  
54    /**
55     * Checks if there are more integers to iterate over
56     * @return true if there are more elements, false otherwise
57     */
58    bool hasNext() {
59        // Check if the next index is within bounds of the flattened list
60        return currentIndex + 1 < flattenedList.size();
61    }
62  
63private:
64    vector<int> flattenedList;  // Stores all integers in flattened form
65    int currentIndex = -1;       // Current position in the iteration, initialized to -1
66};
67
68/**
69 * Your NestedIterator object will be instantiated and called as such:
70 * NestedIterator i(nestedList);
71 * while (i.hasNext()) cout << i.next();
72 */
73
1/**
2 * // This is the interface that allows for creating nested lists.
3 * // You should not implement it, or speculate about its implementation
4 * class NestedInteger {
5 *     If value is provided, then it holds a single integer
6 *     Otherwise it holds an empty nested list
7 *     constructor(value?: number) {
8 *         ...
9 *     };
10 *
11 *     Return true if this NestedInteger holds a single integer, rather than a nested list.
12 *     isInteger(): boolean {
13 *         ...
14 *     };
15 *
16 *     Return the single integer that this NestedInteger holds, if it holds a single integer
17 *     Return null if this NestedInteger holds a nested list
18 *     getInteger(): number | null {
19 *         ...
20 *     };
21 *
22 *     Set this NestedInteger to hold a single integer equal to value.
23 *     setInteger(value: number) {
24 *         ...
25 *     };
26 *
27 *     Set this NestedInteger to hold a nested list and adds a nested integer elem to it.
28 *     add(elem: NestedInteger) {
29 *         ...
30 *     };
31 *
32 *     Return the nested list that this NestedInteger holds,
33 *     or an empty list if this NestedInteger holds a single integer
34 *     getList(): NestedInteger[] {
35 *         ...
36 *     };
37 * };
38 */
39
40// Array to store all flattened integers from the nested list
41let flattenedNumbers: number[] = [];
42
43// Current index pointer for iteration through the flattened array
44let currentIndex: number = -1;
45
46/**
47 * Initializes the iterator with a nested list
48 * Flattens the nested structure into a single-dimensional array
49 * @param nestedList - The input nested list to be flattened
50 */
51function NestedIterator(nestedList: NestedInteger[]): void {
52    // Reset the state for new initialization
53    flattenedNumbers = [];
54    currentIndex = -1;
55  
56    /**
57     * Depth-first search helper function to recursively flatten the nested list
58     * @param list - Current nested list being processed
59     */
60    const depthFirstSearch = (list: NestedInteger[]): void => {
61        // Iterate through each element in the current list
62        for (const element of list) {
63            if (element.isInteger()) {
64                // If element is an integer, add it to the flattened array
65                flattenedNumbers.push(element.getInteger()!);
66            } else {
67                // If element is a nested list, recursively process it
68                depthFirstSearch(element.getList());
69            }
70        }
71    };
72  
73    // Start the flattening process from the root nested list
74    depthFirstSearch(nestedList);
75}
76
77/**
78 * Checks if there are more elements to iterate
79 * @returns true if there are more elements, false otherwise
80 */
81function hasNext(): boolean {
82    // Check if the next index is within the bounds of the flattened array
83    return currentIndex + 1 < flattenedNumbers.length;
84}
85
86/**
87 * Returns the next element in the iteration
88 * @returns The next integer in the flattened sequence
89 */
90function next(): number {
91    // Increment the index and return the element at that position
92    return flattenedNumbers[++currentIndex];
93}
94

Time and Space Complexity

Time Complexity:

  • Constructor (__init__): O(n) where n is the total number of integers in the nested list structure. The DFS traversal visits each element exactly once, whether it's an integer or a nested list. For each integer element, we perform an O(1) append operation.
  • next(): O(1) - Simply increments the index and returns the element at that position in the flattened list.
  • hasNext(): O(1) - Just compares the current index with the length of the list.

Space Complexity:

  • O(n + d) where n is the total number of integers in the nested structure and d is the maximum depth of nesting.
    • O(n) for storing all integers in self.nums list after flattening.
    • O(d) for the recursive call stack during DFS traversal, where d represents the maximum nesting depth.
    • In the worst case where the structure is deeply nested (like [[[[...[[1]]...]]]), the space complexity would be O(n) for both the storage and call stack combined.

Analysis: The approach flattens the entire nested structure upfront during initialization using DFS. This trades initialization time for constant-time iteration operations. All integers are extracted and stored in a simple list, making subsequent next() and hasNext() operations very efficient at O(1) each.

Common Pitfalls

1. Attempting Lazy Evaluation with Incorrect State Management

One common pitfall occurs when developers try to optimize by implementing lazy evaluation (flattening on-the-fly) but fail to properly manage the iterator state across nested levels.

Problematic Approach:

class NestedIterator:
    def __init__(self, nestedList):
        self.stack = nestedList[::-1]  # Reverse for stack operations
  
    def next(self):
        return self.stack.pop().getInteger()  # Assumes top is always an integer
  
    def hasNext(self):
        while self.stack:
            top = self.stack[-1]
            if top.isInteger():
                return True
            self.stack.pop()
            # PITFALL: Losing nested elements
            for item in top.getList()[::-1]:
                self.stack.append(item)
        return False

Issues:

  • The next() method assumes the top element is always an integer
  • State inconsistency between hasNext() and next() calls
  • Complex stack manipulation prone to errors

Solution: Ensure hasNext() always leaves the stack with an integer on top if one exists:

class NestedIterator:
    def __init__(self, nestedList):
        self.stack = nestedList[::-1]
  
    def next(self):
        # hasNext() ensures top is an integer
        return self.stack.pop().getInteger()
  
    def hasNext(self):
        while self.stack:
            top = self.stack[-1]
            if top.isInteger():
                return True
            # Pop the list and expand it
            self.stack.pop()
            nested_list = top.getList()
            for item in nested_list[::-1]:
                self.stack.append(item)
        return False

2. Index Management Errors in Eager Flattening

When using eager flattening (as in the provided solution), a common mistake is incorrect index initialization or increment logic.

Problematic Pattern:

def __init__(self, nestedList):
    self.nums = []
    self.i = 0  # PITFALL: Starting at 0 instead of -1
    # ... flattening logic
  
def next(self):
    result = self.nums[self.i]  # Returns first element twice
    self.i += 1
    return result

Solution: Initialize index to -1 and increment before accessing:

def __init__(self, nestedList):
    self.nums = []
    self.i = -1  # Correct initialization
  
def next(self):
    self.i += 1  # Increment first
    return self.nums[self.i]

3. Memory Inefficiency with Deep Recursion

For deeply nested structures, the recursive DFS approach can cause stack overflow.

Problematic Scenario:

# Deep nesting like: [[[[[[1]]]]]]
def dfs(ls):
    for x in ls:
        if x.isInteger():
            self.nums.append(x.getInteger())
        else:
            dfs(x.getList())  # Can cause stack overflow

Solution: Use iterative approach with explicit stack:

def __init__(self, nestedList):
    self.nums = []
    self.i = -1
  
    # Iterative flattening using stack
    stack = list(reversed(nestedList))
    while stack:
        current = stack.pop()
        if current.isInteger():
            self.nums.append(current.getInteger())
        else:
            # Add nested items to stack in reverse order
            stack.extend(reversed(current.getList()))

4. Handling Empty Nested Lists

Failing to handle empty nested structures correctly can lead to unexpected behavior.

Problematic Assumption:

# Assuming nestedList always has elements
def __init__(self, nestedList):
    self.nums = []
    self.i = -1
    dfs(nestedList)
    # What if self.nums is still empty?

Solution: The current implementation handles this correctly, but ensure client code checks:

iterator = NestedIterator(nestedList)
if iterator.hasNext():  # Always check before calling next()
    value = iterator.next()
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

Which of the following is a good use case for backtracking?


Recommended Readings

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

Load More