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()
: ReturnsTrue
if the element holds a single integer,False
if it holds a nested listgetInteger()
: Returns the integer value if the element is an integergetList()
: Returns the nested list if the element is a list
Your task is to implement the NestedIterator
class with three methods:
__init__(nestedList)
: Constructor that takes the nested list and initializes the iteratornext()
: Returns the next integer in the flattened sequencehasNext()
: ReturnsTrue
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:
- We need to traverse a tree-like structure (nested lists)
- We must visit every element to extract all integers
- The order matters (left-to-right traversal)
- 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.
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:
- Lazy evaluation: Only flatten elements when needed during
next()
orhasNext()
calls - 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()
andhasNext()
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 flatteningself.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 listls
- 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 toself.nums
- If it's a nested list: recursively calls
dfs
withx.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 EvaluatorExample 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 seti = -1
- Call
dfs([[1,1],2,[1,1]])
Step 2: DFS Traversal
Processing element 0: [1,1]
isInteger()
returnsFalse
→ it's a list- Recursively call
dfs([1,1])
- Process first
1
:isInteger()
returnsTrue
→ append tonums = [1]
- Process second
1
:isInteger()
returnsTrue
→ append tonums = [1,1]
- Process first
Processing element 1: 2
isInteger()
returnsTrue
→ append tonums = [1,1,2]
Processing element 2: [1,1]
isInteger()
returnsFalse
→ it's a list- Recursively call
dfs([1,1])
- Process first
1
:isInteger()
returnsTrue
→ append tonums = [1,1,2,1]
- Process second
1
:isInteger()
returnsTrue
→ append tonums = [1,1,2,1,1]
- Process first
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
Operation | Action | i value | Return value | nums[i] |
---|---|---|---|---|
hasNext() | Check if i+1 < 5 → 0 < 5 | -1 | True | - |
next() | Increment i , return nums[0] | 0 | 1 | 1 |
hasNext() | Check if i+1 < 5 → 1 < 5 | 0 | True | - |
next() | Increment i , return nums[1] | 1 | 1 | 1 |
next() | Increment i , return nums[2] | 2 | 2 | 2 |
next() | Increment i , return nums[3] | 3 | 1 | 1 |
next() | Increment i , return nums[4] | 4 | 1 | 1 |
hasNext() | Check if i+1 < 5 → 5 < 5 | 4 | False | - |
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)
wheren
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 anO(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)
wheren
is the total number of integers in the nested structure andd
is the maximum depth of nesting.O(n)
for storing all integers inself.nums
list after flattening.O(d)
for the recursive call stack during DFS traversal, whered
represents the maximum nesting depth.- In the worst case where the structure is deeply nested (like
[[[[...[[1]]...]]]
), the space complexity would beO(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()
andnext()
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()
Which of the following is a good use case for backtracking?
Recommended Readings
Stack Intro Imagine you have a pile of books on your desk If you want to add a new book you place it on top If you want to read a book you take it from the top And if you simply want to see which book is on the top you
Everything About Trees A tree is a type of graph data structure composed of nodes and edges Its main properties are It is acyclic doesn't contain any cycles There exists a path from the root to any node Has N 1 edges where N is the number of nodes in the tree and
https assets algo monster cover_photos dfs svg Depth First Search Prereqs Recursion Review problems recursion_intro Trees problems tree_intro With a solid understanding of recursion under our belts we are now ready to tackle one of the most useful techniques in coding interviews Depth First Search DFS As the name suggests
Want a Structured Path to Master System Design Too? Don’t Miss This!