Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

341. Flatten Nested List Iterator - Leetcode Solution

Code Implementation

# This is the interface that allows for creating nested lists.
# You should not implement it, or speculate about its implementation.
class NestedInteger:
    def isInteger(self) -> bool:
        pass
    def getInteger(self) -> int:
        pass
    def getList(self) -> ['NestedInteger']:
        pass

class NestedIterator:
    def __init__(self, nestedList: [NestedInteger]):
        self.stack = []
        self._pushList(nestedList)

    def _pushList(self, nestedList):
        # push in reverse order so that we can pop from the end (stack)
        for ni in reversed(nestedList):
            self.stack.append(ni)

    def next(self) -> int:
        self.hasNext()
        return self.stack.pop().getInteger()

    def hasNext(self) -> bool:
        while self.stack:
            top = self.stack[-1]
            if top.isInteger():
                return True
            self.stack.pop()
            self._pushList(top.getList())
        return False
      
/**
 * // This is the interface that allows for creating nested lists.
 * // You should not implement it, or speculate about its implementation.
 * class NestedInteger {
 *   public:
 *     // Return true if this NestedInteger holds a single integer, rather than a nested list.
 *     bool isInteger() const;
 *     // Return the single integer that this NestedInteger holds, if it holds a single integer
 *     int getInteger() const;
 *     // Return the nested list that this NestedInteger holds, if it holds a nested list
 *     const vector<NestedInteger> &getList() const;
 * };
 */
class NestedIterator {
    stack<NestedInteger> stk;
public:
    NestedIterator(vector<NestedInteger> &nestedList) {
        pushList(nestedList);
    }
    
    void pushList(const vector<NestedInteger>& nestedList) {
        for (auto it = nestedList.rbegin(); it != nestedList.rend(); ++it) {
            stk.push(*it);
        }
    }

    int next() {
        hasNext();
        int val = stk.top().getInteger();
        stk.pop();
        return val;
    }
    
    bool hasNext() {
        while (!stk.empty()) {
            NestedInteger &top = stk.top();
            if (top.isInteger()) {
                return true;
            }
            stk.pop();
            pushList(top.getList());
        }
        return false;
    }
};
      
/**
 * // This is the interface that allows for creating nested lists.
 * // You should not implement it, or speculate about its implementation.
 * public interface NestedInteger {
 *     public boolean isInteger();
 *     public Integer getInteger();
 *     public List<NestedInteger> getList();
 * }
 */
public class NestedIterator implements Iterator<Integer> {
    private Deque<NestedInteger> stack = new LinkedList<>();

    public NestedIterator(List<NestedInteger> nestedList) {
        pushList(nestedList);
    }

    private void pushList(List<NestedInteger> nestedList) {
        for (int i = nestedList.size() - 1; i >= 0; i--) {
            stack.push(nestedList.get(i));
        }
    }

    @Override
    public Integer next() {
        hasNext();
        return stack.pop().getInteger();
    }

    @Override
    public boolean hasNext() {
        while (!stack.isEmpty()) {
            NestedInteger top = stack.peek();
            if (top.isInteger()) {
                return true;
            }
            stack.pop();
            pushList(top.getList());
        }
        return false;
    }
}
      

/**
 * // This is the interface that allows for creating nested lists.
 * // You should not implement it, or speculate about its implementation.
 * function NestedInteger() {
 *     // Return true if this NestedInteger holds a single integer, rather than a nested list.
 *     // @return {boolean}
 *     this.isInteger = function() {...};
 *     // Return the single integer that this NestedInteger holds, if it holds a single integer
 *     // @return {integer}
 *     this.getInteger = function() {...};
 *     // Return the nested list that this NestedInteger holds, if it holds a nested list
 *     // @return {NestedInteger[]}
 *     this.getList = function() {...};
 * };
 */

var NestedIterator = function(nestedList) {
    this.stack = [];
    this._pushList = function(list) {
        for (let i = list.length - 1; i >= 0; i--) {
            this.stack.push(list[i]);
        }
    }
    this._pushList(nestedList);
};

NestedIterator.prototype.hasNext = function() {
    while (this.stack.length > 0) {
        let top = this.stack[this.stack.length - 1];
        if (top.isInteger()) {
            return true;
        }
        this.stack.pop();
        this._pushList(top.getList());
    }
    return false;
};

NestedIterator.prototype.next = function() {
    this.hasNext();
    return this.stack.pop().getInteger();
};
      

Problem Description

The Flatten Nested List Iterator problem asks you to implement an iterator over a nested list of integers. Each element in the list is either an integer or another nested list (which can, recursively, contain more integers or lists). You are given an initial nested list, and you must design a class that:

  • Has a next() method that returns the next integer in the nested structure.
  • Has a hasNext() method that returns True (or true) if there are still integers left to iterate over.

The iterator should flatten the list on-the-fly, meaning you should not flatten the entire structure at initialization. You must not reuse or skip any elements, and each integer should be returned exactly once, in the order they appear in a left-to-right depth-first traversal of the nested structure.

Thought Process

At first glance, this problem looks like a tree traversal, since the nested lists can be arbitrarily deep. One obvious (but suboptimal) approach is to flatten the entire nested list into a single array at the start, and then just iterate over it. However, this uses extra memory and does not meet the requirement to flatten "on-the-fly".

To do this efficiently, we need a way to traverse the nested structure as we go, keeping track of our current position and only expanding lists when necessary. This is similar to using a stack for depth-first search (DFS) in trees. The challenge is to make hasNext() and next() both efficient and correct, even when the next integer is buried several levels deep in nested lists.

Solution Approach

We can use a stack to keep track of where we are in the nested structure. Each item on the stack is a NestedInteger object. Here’s how the algorithm works:

  1. Initialization: Push all the elements of the initial nestedList onto the stack in reverse order (so the first element is on top).
  2. hasNext(): While the stack is not empty:
    • Peek at the top of the stack.
    • If it’s an integer, return True (we have a next integer).
    • If it’s a list, pop it from the stack and push its contents onto the stack in reverse order.
    • Repeat until you find an integer or the stack is empty.
  3. next(): Call hasNext() to ensure the top is an integer, then pop and return it.

This method ensures that we only expand lists when needed, and always return the next integer in the correct order.

Why use a stack? Because stacks naturally support depth-first traversal. By pushing elements in reverse order, we ensure the leftmost elements are processed first.

Example Walkthrough

Suppose the input is [[1,1],2,[1,1]]. Here’s how the iterator works:

  1. Initialize: Push [1,1], 2, [1,1] onto the stack (in reverse order).
  2. hasNext(): Top is [1,1] (a list). Pop it, push 1, 1 onto the stack.
  3. Top is 1 (an integer). hasNext() returns True.
  4. next(): Pop and return 1.
  5. Repeat: Next 1 (integer), then 2 (integer).
  6. Next [1,1] (list): Pop and push 1, 1.
  7. Return the remaining 1, 1.

The iterator returns: 1, 1, 2, 1, 1.

Time and Space Complexity

Brute-force approach: Flatten the entire list at the start. Time: O(N), Space: O(N), where N is the total number of integers.

Optimized stack-based approach:

  • hasNext()/next(): Each integer is pushed and popped from the stack at most once, so the total time over all calls is O(N).
  • Space: O(D), where D is the maximum depth of the nested list at any moment (since we only keep the current traversal path on the stack).

The solution is both time and space efficient, as it only expands lists as needed.

Summary

The Flatten Nested List Iterator problem is a classic example of using a stack to simulate recursive traversal in an iterative way. By pushing elements in reverse order and only expanding lists when needed, we ensure efficient, on-the-fly flattening of deeply nested structures. This approach is elegant because it is both time and space efficient, and it mirrors the logic of depth-first search in trees.