Editorial
Core insight
Flatten Nested List Iterator · Design Patterns
Core Insight for Flatten Nested List Iterator
The core insight required to move from brute force to the optimal LeetCode 341 solution is to simulate the recursion stack explicitly.
When we write a recursive function to traverse a tree or nested list, the system call stack tracks where we are in the recursion. To implement an iterator (which must pause and resume via next() and hasNext()), we cannot rely on the system call stack because the function returns after every single element.
Therefore, we must manage our own Stack.
The key invariant we must enforce is: The top of the stack is always the next element to be processed.
- If the top is an Integer, it is ready to be returned by
next(). - If the top is a List, it is not ready. We must pop the list, break it open, and push its contents back onto the stack.
Visual Description: Imagine the stack as a "To-Do" list. Initially, the entire input list is the "To-Do" item.
- We look at the top item. Is it a single number? If yes, we are done preparing.
- Is it a list? If yes, we remove the list "container" and place all its items onto the stack individually.
- Crucially, because a Stack is LIFO (Last-In, First-Out), we must push the items of the list in reverse order. This ensures the first element of the list ends up at the top of the stack.
Deep dive
Full breakdown
Full reasoning, pitfalls, and implementation flow
LeetCode 341: Flatten Nested List Iterator Solution & Explanation
Problem Overview
TL;DR: The optimal solution uses a stack to implement the Iterator Pattern, enabling lazy evaluation of the nested structure by flattening lists only when necessary.
The LeetCode 341 problem asks us to design an iterator for a nested list of integers. The input is a list where each element is either a single integer or another list (which itself can contain integers or lists). Our goal is to implement a class NestedIterator that flattens this structure on the fly, providing next() and hasNext() methods to traverse the integers sequentially as if they were in a single flat list.
This is a popular interview question because it tests object-oriented design skills and the ability to handle recursive structures iteratively.
Brute Force Approach for Flatten Nested List Iterator
The most straightforward approach is to flatten the entire nested structure upfront inside the constructor. We can use a recursive Depth-First Search (DFS) function to traverse the nestedList and store all integers in a simple linear list or queue.
Naive Logic
- Constructor: Initialize a result list. Call a recursive helper function on the input
nestedList. - Helper Function: Loop through the current list. If an element is an integer, add it to the result. If it is a list, recursively call the helper on that list.
- next(): Return the element at the current pointer index and increment the pointer.
- hasNext(): Check if the pointer index is less than the result list size.
Why this approach is suboptimal
While this passes the basic correctness check, it fails the primary design requirement of an Iterator.
- Not Lazy: An iterator should be lazy; it should only compute the next value when requested. If the nested list contains millions of items, the brute force approach processes them all immediately, causing a massive initial delay.
- Memory Inefficiency: This approach creates a full copy of all integers in memory ( space), duplicating the data already present in the input structure.
- Infinite Streams: If the data structure were a stream or theoretically infinite, the brute force constructor would never terminate.
Algorithm Strategy: Design Patterns
We will implement the NestedIterator class using a Stack<NestedInteger>.
- State Management: The class maintains a single stack.
- Initialization: In the constructor, we push all elements of the input
nestedListonto the stack. We iterate from the back of the list to the front, pushing elements so that the first element of the list is at the top of the stack. - Ensuring Validity (
hasNext): ThehasNext()method is responsible for "preparing" the stack. It loops as long as the stack is not empty and the top element is a nested list (not an integer). Inside this loop, it pops the nested list and pushes its contents (in reverse order) back onto the stack.- If the stack becomes empty during this unpacking (e.g., a list of empty lists
[[], []]), it returnsfalse. - If the top of the stack becomes an integer, it returns
true.
- If the stack becomes empty during this unpacking (e.g., a list of empty lists
- Retrieving Values (
next): This method simply assumeshasNext()has done the work. It checks ifhasNext()is true, then pops the top element (which is guaranteed to be an integer) and returns it.
Execution Flow
Consider the input [[1,1], 2, [1,1]].
-
Constructor:
- Iterate input backwards.
- Push
[1,1](the last one). - Push
2. - Push
[1,1](the first one). - Stack State (Top to Bottom):
[[1,1], 2, [1,1]]
-
Call
hasNext():- Top is
[1,1](List). Pop it. - Push contents reversed: Push
1, then Push1. - Stack State:
[1, 1, 2, [1,1]] - Top is
1(Integer). Stop and returntrue.
- Top is
-
Call
next():- Pop top (
1). Return1. - Stack State:
[1, 2, [1,1]]
- Pop top (
-
Call
hasNext():- Top is
1(Integer). Returntrue.
- Top is
-
Call
next():- Pop top (
1). Return1. - Stack State:
[2, [1,1]]
- Pop top (
-
Call
hasNext():- Top is
2(Integer). Returntrue.
- Top is
-
Call
next():- Pop top (
2). Return2. - Stack State:
[[1,1]]
- Pop top (
-
Call
hasNext():- Top is
[1,1](List). Pop it. - Push contents reversed: Push
1, then Push1. - Stack State:
[1, 1] - Top is
1(Integer). Returntrue.
- Top is
This process continues until the stack is empty.
Proof of Correctness
The correctness relies on the simulation of Depth-First Search. By pushing list elements onto the stack in reverse order, we ensure that when they are popped (LIFO), they are processed in the original forward order. The hasNext() method acts as a "normalizer," ensuring the invariant that the stack top is always a primitive integer before next() attempts to access it. This covers edge cases like empty lists ([[]]) because the loop in hasNext() will simply unpack the empty list, find nothing to push back, and continue to the next element in the stack.
Pattern Reuse Notes
The "Design" pattern with auxiliary data structures (Stack/Map/Queue) is critical for several LeetCode problems:
- LeetCode 146: LRU Cache: Uses a HashMap combined with a Doubly Linked List to manage state for access and eviction.
- LeetCode 155: Min Stack: Uses an auxiliary stack to track the minimum value at every state, similar to how we track nesting here.
- LeetCode 225: Implement Stack using Queues: Requires manipulating internal storage to mimic a different data structure's behavior.
- LeetCode 232: Implement Queue using Stacks: Similar to the iterator, this uses two stacks to lazily move elements to achieve amortized operations.
Feynman coach · Before you peek at the code
Can you explain the core insight in your own words?
If you can explain it before reading code, you’re far more likely to recall it under interview pressure.
Reference Implementation
C++ Solution for LeetCode 341
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 {
20private:
21 // Stack stores NestedInteger objects.
22 // Top of the stack is always the next element to process.
23 stack<NestedInteger> s;
24
25public:
26 NestedIterator(vector<NestedInteger> &nestedList) {
27 // Push all elements to stack in reverse order to maintain correct sequence
28 // when popped (LIFO).
29 for (int i = nestedList.size() - 1; i >= 0; --i) {
30 s.push(nestedList[i]);
31 }
32 }
33
34 int next() {
35 // hasNext() guarantees the top is an integer
36 int result = s.top().getInteger();
37 s.pop();
38 return result;
39 }
40
41 bool hasNext() {
42 // Ensure the top of the stack is an integer.
43 // If it's a list, unpack it.
44 while (!s.empty()) {
45 NestedInteger top = s.top();
46
47 // If top is an integer, we are ready.
48 if (top.isInteger()) {
49 return true;
50 }
51
52 // If top is a list, pop it and push its contents in reverse order.
53 s.pop();
54 const vector<NestedInteger>& list = top.getList();
55 for (int i = list.size() - 1; i >= 0; --i) {
56 s.push(list[i]);
57 }
58 }
59 return false;
60 }
61};Java Solution for LeetCode 341
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 */
18import java.util.*;
19
20public class NestedIterator implements Iterator<Integer> {
21 // Deque is preferred over Stack in Java
22 private Deque<NestedInteger> stack;
23
24 public NestedIterator(List<NestedInteger> nestedList) {
25 stack = new ArrayDeque<>();
26 flatten(nestedList);
27 }
28
29 // Helper to push elements in reverse order
30 private void flatten(List<NestedInteger> list) {
31 for (int i = list.size() - 1; i >= 0; i--) {
32 stack.push(list.get(i));
33 }
34 }
35
36 @Override
37 public Integer next() {
38 if (!hasNext()) throw new NoSuchElementException();
39 return stack.pop().getInteger();
40 }
41
42 @Override
43 public boolean hasNext() {
44 while (!stack.isEmpty()) {
45 NestedInteger top = stack.peek();
46
47 // If the top is an integer, we are ready
48 if (top.isInteger()) {
49 return true;
50 }
51
52 // If the top is a list, pop it and unpack
53 stack.pop();
54 flatten(top.getList());
55 }
56 return false;
57 }
58}Python Solution for LeetCode 341
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
23class NestedIterator:
24 def __init__(self, nestedList: [NestedInteger]):
25 # Initialize stack with the list reversed so the first element is at the end (top)
26 self.stack = list(reversed(nestedList))
27
28 def next(self) -> int:
29 # hasNext() ensures the top (last element) is an integer
30 return self.stack.pop().getInteger()
31
32 def hasNext(self) -> bool:
33 # While the stack is not empty, check the top element
34 while self.stack:
35 top = self.stack[-1]
36
37 # If it's an integer, we are ready
38 if top.isInteger():
39 return True
40
41 # If it's a list, pop it and extend the stack with the list elements
42 # We must extend with reversed list to maintain order
43 self.stack.pop()
44 nested_list = top.getList()
45 for i in range(len(nested_list) - 1, -1, -1):
46 self.stack.append(nested_list[i])
47
48 return False