|
| 1 | +# Flatten Nested List Iterator |
| 2 | + |
| 3 | +You are given a nested list of integers nestedList. Each element is either an integer or a list whose elements may also |
| 4 | +be integers or other lists. Implement an iterator to flatten it. |
| 5 | + |
| 6 | +Implement the NestedIterator class: |
| 7 | + |
| 8 | +- NestedIterator(List<NestedInteger> nestedList) Initializes the iterator with the nested list nestedList. |
| 9 | +- int next() Returns the next integer in the nested list. |
| 10 | +- boolean hasNext() Returns true if there are still some integers in the nested list and false otherwise. |
| 11 | + |
| 12 | +Your code will be tested with the following pseudocode: |
| 13 | + |
| 14 | +> initialize iterator with nestedList |
| 15 | +> res = [] |
| 16 | +> while iterator.hasNext() |
| 17 | + append iterator.next() to the end of res |
| 18 | +> return res |
| 19 | +
|
| 20 | +If `res` matches the expected flattened list, then your code will be judged as correct. |
| 21 | + |
| 22 | +## Examples |
| 23 | + |
| 24 | +Example 1: |
| 25 | + |
| 26 | +```text |
| 27 | +Input: nestedList = [[1,1],2,[1,1]] |
| 28 | +Output: [1,1,2,1,1] |
| 29 | +Explanation: By calling next repeatedly until hasNext returns false, the order of elements returned by next should be: |
| 30 | +[1,1,2,1,1]. |
| 31 | +``` |
| 32 | + |
| 33 | +Example 2: |
| 34 | +```text |
| 35 | +Input: nestedList = [1,[4,[6]]] |
| 36 | +Output: [1,4,6] |
| 37 | +Explanation: By calling next repeatedly until hasNext returns false, the order of elements returned by next should be: |
| 38 | +[1,4,6]. |
| 39 | +``` |
| 40 | + |
| 41 | +## Constraints |
| 42 | + |
| 43 | +- 1 <= nestedList.length <= 500 |
| 44 | +- The values of the integers in the nested list is in the range [-10^6, 10^6]. |
| 45 | + |
| 46 | +## Topics |
| 47 | + |
| 48 | +- Stack |
| 49 | +- Tree |
| 50 | +- Depth-First Search |
| 51 | +- Design |
| 52 | +- Queue |
| 53 | +- Iterator |
| 54 | + |
| 55 | +## Solution(s) |
| 56 | + |
| 57 | +1. [Depth-First Search Approach](#depth-first-search-approach) |
| 58 | +2. [Stack Approach](#stack-approach) |
| 59 | + |
| 60 | +### Depth-first Search Approach |
| 61 | + |
| 62 | +When we look at this nested list structure, we're essentially dealing with a tree where each node can either be a leaf |
| 63 | +(integer) or a branch (nested list). The challenge is to visit every integer in the correct order, just like reading a |
| 64 | +book - we read each word sequentially, even though the book has a hierarchical structure of chapters, sections, and |
| 65 | +paragraphs. |
| 66 | + |
| 67 | +The key insight is recognizing that we need to "flatten" this hierarchical structure into a linear sequence. Think of it |
| 68 | +like unpacking nested boxes - we open the main box, and for each item inside, if it's another box, we open that too, |
| 69 | +continuing until we find all the actual items (integers). |
| 70 | + |
| 71 | +Since we need to access elements one by one through next() calls, we have two main strategies: |
| 72 | + |
| 73 | +- Lazy evaluation: Only flatten elements when needed during next() or hasNext() calls |
| 74 | +- Eager evaluation: Flatten everything upfront during initialization |
| 75 | + |
| 76 | +The eager approach is simpler and more intuitive here. Why? Because: |
| 77 | + |
| 78 | +- We're going to visit all elements anyway (the iterator will traverse the entire structure) |
| 79 | +- Pre-flattening makes next() and hasNext() operations trivial - just array indexing |
| 80 | +- We avoid the complexity of maintaining state about partially explored nested lists |
| 81 | + |
| 82 | +The DFS pattern naturally fits this problem because it mirrors how we'd manually flatten the list: when we encounter a |
| 83 | +nested list, we immediately dive into it, process all its contents, then continue with the next element at the current |
| 84 | +level. This is exactly what DFS does - it goes as deep as possible before backtracking. |
| 85 | + |
| 86 | +The recursive nature of DFS perfectly matches the recursive structure of nested lists. Each recursive call handles one |
| 87 | +level of nesting, and the base case (finding an integer) is where we actually collect the values into our flattened |
| 88 | +result. |
| 89 | + |
| 90 | +#### Complexity Analysis |
| 91 | + |
| 92 | +##### Time Complexity |
| 93 | + |
| 94 | +- Constructor: `O(n)` where n is the total number of integers in the nested list structure. The DFS traversal visits |
| 95 | + each element exactly once, whether it's an integer or a nested list. For each integer element, we perform an `O(1)` |
| 96 | + append operation. |
| 97 | +- next(): `O(1)` - Simply increments the index and returns the element at that position in the flattened list. |
| 98 | +- hasNext(): `O(1) `- Just compares the current index with the length of the list. |
| 99 | + |
| 100 | +##### Space Complexity |
| 101 | + |
| 102 | +- `O(n + d)` where n is the total number of integers in the nested structure and d is the maximum depth of nesting. |
| 103 | + - `O(n)` for storing all integers in `self.flattened_list` list after flattening. |
| 104 | + - `O(d)` for the recursive call stack during DFS traversal, where d represents the maximum nesting depth. |
| 105 | + - In the worst case where the structure is deeply nested (like `[[[[...[[1]]...]]]`), the space complexity would be |
| 106 | + `O(n)` for both the storage and call stack combined. |
| 107 | + |
| 108 | +Analysis: The approach flattens the entire nested structure upfront during initialization using DFS. This trades |
| 109 | +initialization time for constant-time iteration operations. All integers are extracted and stored in a simple list, |
| 110 | +making subsequent next() and hasNext() operations very efficient at O(1) each. |
| 111 | + |
| 112 | +--- |
| 113 | + |
| 114 | +### Stack Approach |
| 115 | + |
| 116 | +We’ll use a stack to solve this problem. The stack will be used to store the integer and list of integers on the iterator |
| 117 | +object. We’ll push all the nested list data in the stack in reverse order in the constructor. The elements are pushed in |
| 118 | +reverse order because the iterator is implemented using a stack. In order to process the nested list correctly, the |
| 119 | +elements need to be accessed in the order they appear in the original nested list. |
| 120 | + |
| 121 | +Here is how we implement the NestedIterator class methods to solve the above problem: |
| 122 | + |
| 123 | +**Constructor** |
| 124 | + |
| 125 | +1. The constructor initializes an empty stack of NestedInteger objects. |
| 126 | +2. The constructor iterates through the input nestedList, starting from the last element to the first element |
| 127 | + (reverse order). It pushes each element onto the stack using stack.push(). |
| 128 | + |
| 129 | +**`hasNext()` method** |
| 130 | + |
| 131 | +The hasNext() method checks if there is a next integer to return from the stack, iterating through nested lists if |
| 132 | +necessary. |
| 133 | + |
| 134 | +1. The top element of the stack is retrieved using stack.peek(). |
| 135 | +2. The top element is checked using the method top.isInteger(): |
| 136 | + - If it is an integer, the method returns TRUE because the next element is an integer. |
| 137 | + - The top element must be a nested list if it is not an integer. In this case: |
| 138 | + - The top element is popped from the stack using stack.pop(). |
| 139 | + - The list is retrieved using top.getList(). |
| 140 | + - The elements of this nested list are pushed onto the stack in reverse order using stack.push(). This ensures that |
| 141 | + the first element of the nested list will be processed first. |
| 142 | +3. If the stack is empty, the method returns FALSE. |
| 143 | + |
| 144 | +**`next()` method** |
| 145 | + |
| 146 | +The next() method returns the next integer from the stack. |
| 147 | +1. The method calls hasNext() to check if more integers are available. If hasNext() returns true, the method pops the |
| 148 | + top element of the stack using stack.pop(), which will be an integer because hasNext() ensures that any nested lists |
| 149 | + are flattened and only integers remain in the stack. |
| 150 | +2. If hasNext() returns false, the method returns 0. |
| 151 | + |
| 152 | +#### Complexity Analysis |
| 153 | + |
| 154 | +##### Time Complexity |
| 155 | + |
| 156 | +Assume |
| 157 | +- n is the number of elements, |
| 158 | +- l is the number of nested lists, and |
| 159 | +- d is the maximum nesting depth (maximum number of lists inside each other). |
| 160 | + |
| 161 | +**Constructor**: Since the constructor pushes all of the elements from the nested list into the stack, the total time |
| 162 | +will be the size of that list. Therefore, the time complexity will be `O(n+l)`. |
| 163 | + |
| 164 | +**Has Next ()**: This function will be called several times. During all of these calls, the function will iterate over |
| 165 | +all the lists exactly once (the while loop) and process every integer exactly once. Thus, a total of `O(n+l)` effort is |
| 166 | +spent. The iterator will be progressed for every integer in the nested list. Thus, there will be a total of `O(n)` calls. |
| 167 | +Accordingly, the running time for one call to Has Next is `O((n+l)/n)=O(l/n)`. |
| 168 | + |
| 169 | +Next (): This function calls the Has Next function every time. So, its complexity will be the same as that of the Has |
| 170 | +Next function, that is, `O(l/n)`. |
| 171 | + |
| 172 | +##### Space Complexity |
| 173 | + |
| 174 | +The space complexity will be `O(n+l)` |
| 175 | + |
| 176 | +In the worst-case scenario, whereby the outermost list contains |
| 177 | +n integers or l empty sub-lists, it will cost `O(n+l)` space. Other expensive cases occur when the nesting is very deep. |
| 178 | +It’s useful to remember that d≤l (because each layer of nesting requires another list), but we don’t need to consider |
| 179 | +this for our case. |
0 commit comments