Iterator for Combination
MedPrompt
Design the CombinationIterator class:
CombinationIterator(string characters, int combinationLength)Initializes the object with a stringcharactersof sorted distinct lowercase English letters and a numbercombinationLengthas arguments.next()Returns the next combination of lengthcombinationLengthin lexicographical order.hasNext()Returnstrueif and only if there exists a next combination.
Example 1:
Input
["CombinationIterator", "next", "hasNext", "next", "hasNext", "next", "hasNext"]
[["abc", 2], [], [], [], [], [], []]
Output
[null, "ab", true, "ac", true, "bc", false]
Explanation
CombinationIterator itr = new CombinationIterator("abc", 2);
itr.next(); // return "ab"
itr.hasNext(); // return True
itr.next(); // return "ac"
itr.hasNext(); // return True
itr.next(); // return "bc"
itr.hasNext(); // return False
Constraints:
1 <= combinationLength <= characters.length <= 15- All the characters of
charactersare unique. - At most
104calls will be made tonextandhasNext. - It is guaranteed that all calls of the function
nextare valid.
Approaches
2 approaches with complexity analysis and trade-offs.
This highly efficient approach avoids pre-computing all combinations. Instead, it calculates the next combination only when the next() method is called. It maintains the state of the current combination using an array of indices that point to characters in the input string, a technique often used for generating combinations iteratively.
Algorithm
CombinationIterator(characters, combinationLength):- Store
characters, its lengthn, andcombinationLengthk. - Initialize an integer array
indicesof sizek. - Populate
indicesto represent the first combination:indices[i] = iforifrom0tok-1. - Set a boolean flag
hasNextFlag = true.
- Store
next():- Build the result string for the current combination by looking up
charactersusing the values in theindicesarray. - Prepare the
indicesfor the next call. Find the rightmost indexithat can be incremented. An indexindices[i]can be incremented if it's not at its maximum possible value, which isi + n - k. - Scan from
i = k-1down to0to find the first index that is not at its maximum value. - If no such index is found, it means the current combination is the last one. Set
hasNextFlag = false. - If an index
iis found: a. Incrementindices[i]. b. For all subsequent indicesj > i, setindices[j] = indices[j-1] + 1to form the next lexicographically smallest combination. - Return the string built in step 1.
- Build the result string for the current combination by looking up
hasNext():- Return the
hasNextFlag.
- Return the
Walkthrough
The state of the iterator is represented by an array of integers, indices, of size combinationLength. Each integer in this array is an index into the original characters string. The constructor initializes this array to [0, 1, ..., k-1], representing the first lexicographical combination.
The next() method performs two main tasks: it first constructs the string for the current indices and then updates the indices array to point to the next combination. To find the next combination, we scan the indices array from right to left, looking for the first index we can increment. Once we find such an index, we increment it and then reset all subsequent indices to be consecutive values following it. This guarantees that we move to the very next combination in lexicographical order. If we scan the whole array and find that all indices are at their maximum possible values, we know there are no more combinations left.
class CombinationIterator { private String characters; private int n; private int k; private int[] indices; private boolean hasNext; public CombinationIterator(String characters, int combinationLength) { this.characters = characters; this.n = characters.length(); this.k = combinationLength; this.indices = new int[k]; // Initialize to the first combination: [0, 1, ..., k-1] for (int i = 0; i < k; i++) { this.indices[i] = i; } this.hasNext = true; } public String next() { // Build the current combination string StringBuilder sb = new StringBuilder(); for (int index : indices) { sb.append(characters.charAt(index)); } // Find the next combination's indices for the subsequent call // Start from the rightmost index and find the first one that can be incremented. int i = k - 1; while (i >= 0 && indices[i] == i + n - k) { i--; } if (i < 0) { // This was the last combination this.hasNext = false; } else { // Increment this index indices[i]++; // Reset all subsequent indices for (int j = i + 1; j < k; j++) { indices[j] = indices[j - 1] + 1; } } return sb.toString(); } public boolean hasNext() { return this.hasNext; }}Complexity
Time
Constructor: `O(K)` to initialize the `indices` array. `next()`: `O(K)`. In the worst case, we scan the entire `indices` array (`O(K)`) to find the pivot, and building the result string also takes `O(K)`. `hasNext()`: `O(1)`.
Space
O(K), where K is the `combinationLength`, to store the `indices` array.
Trade-offs
Pros
Extremely memory efficient, using only space proportional to the combination length.
Fast initialization (
O(K)), as no heavy computation is done upfront.Work is distributed across
next()calls (lazy evaluation), which is ideal for typical iterator usage patterns.
Cons
The logic inside the
next()method is more complex than in the pre-computation approach.Each
next()call has a slightly higher cost (O(K)) compared to theO(1)of the pre-computation approach.
Solutions
Solution
class CombinationIterator { private int n ; private int combinationLength ; private String characters ; private StringBuilder t = new StringBuilder (); private List < String > cs = new ArrayList <>(); private int idx = 0 ; public CombinationIterator ( String characters , int combinationLength ) { n = characters . length (); this . combinationLength = combinationLength ; this . characters = characters ; dfs ( 0 ); } public String next () { return cs . get ( idx ++); } public boolean hasNext () { return idx < cs . size (); } private void dfs ( int i ) { if ( t . length () == combinationLength ) { cs . add ( t . toString ()); return ; } if ( i == n ) { return ; } t . append ( characters . charAt ( i )); dfs ( i + 1 ); t . deleteCharAt ( t . length () - 1 ); dfs ( i + 1 ); } } /** * Your CombinationIterator object will be instantiated and called as such: * CombinationIterator obj = new CombinationIterator(characters, combinationLength); * String param_1 = obj.next(); * boolean param_2 = obj.hasNext(); */Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.