Design Compressed String Iterator solution and explanation
3 more videos on this problem
LeetCode 604. Design Compressed String Iterator Explanation and Solutionhappygirlzt · 4:23 · 914 views
Design Compressed String IteratorScott · 9:04 · 448 views
Leetcode 604. Design Compressed String Iterator (list groups)LetsCode · 8:33 · 204 views
604. Design Compressed String Iterator (LeetCode, Java)hakunamatasq · 7:51 · 63 views
The problem
Design and implement a data structure for a compressed string iterator. The given compressed string will be in the form of each letter followed by a positive integer representing the number of this letter existing in the original uncompressed string.
Implement the StringIterator class:
next()Returns the next character if the original string still has uncompressed characters, otherwise returns a white space.hasNext()Returns true if there is any letter needs to be uncompressed in the original string, otherwise returnsfalse.
Example 1:
Input
["StringIterator", "next", "next", "next", "next", "next", "next", "hasNext", "next", "hasNext"]
[["L1e2t1C1o1d1e1"], [], [], [], [], [], [], [], [], []]
Output
[null, "L", "e", "e", "t", "C", "o", true, "d", true]
Explanation
StringIterator stringIterator = new StringIterator("L1e2t1C1o1d1e1");
stringIterator.next(); // return "L"
stringIterator.next(); // return "e"
stringIterator.next(); // return "e"
stringIterator.next(); // return "t"
stringIterator.next(); // return "C"
stringIterator.next(); // return "o"
stringIterator.hasNext(); // return True
stringIterator.next(); // return "d"
stringIterator.hasNext(); // return True
Constraints:
1 <= compressedString.length <= 1000compressedStringconsists of lower-case an upper-case English letters and digits.- The number of a single character repetitions in
compressedStringis in the range[1, 10^9] - At most
100calls will be made tonextandhasNext.
Approach overview
Problem Overview: You receive a run-length encoded string such as a2b3 where characters are followed by counts. Build an iterator that returns the next character in the decompressed sequence using next() and checks availability using hasNext() without explicitly storing the full expanded string.
Approach 1: Fully Decompress the String (O(n) time, O(n) space)
The most direct solution expands the compressed string into a normal string during initialization. Iterate through the input, parse each character and its numeric count, then append the character count times to a buffer. The iterator simply moves a pointer across this expanded string for next() while hasNext() checks if the pointer is within bounds. This works but wastes memory when counts are large since the decompressed string could be huge compared to the compressed input.
Approach 2: Parsing and Storing Character Counts (O(n) preprocessing, O(1) per operation, O(k) space)
Instead of expanding the string, parse the compressed input once and store pairs of (character, remainingCount). Maintain an index pointing to the current character block. When next() is called, return the current character and decrement its remaining count. If the count reaches zero, advance the index to the next pair. The hasNext() method checks whether the current index is still within the parsed array. This keeps memory proportional to the number of compressed segments rather than the full expanded length.
This approach is a classic design problem where the constructor performs parsing and the iterator methods maintain internal state. It also demonstrates efficient handling of run-length encoding using basic string parsing and sequential access patterns similar to an array iterator.
Recommended for interviews: The parsing-and-count-tracking approach is what interviewers expect. It avoids unnecessary memory allocation while keeping each iterator operation constant time. Mentioning the naive decompression method first shows you understand the problem baseline, but implementing the count-based iterator demonstrates stronger design and efficiency skills.
Solution
Parse the compressedString into characters c and their corresponding repetition counts x, and store them in an array or list d. Use p to point to the current character.
Then perform operations in next and hasNext.
The initialization time complexity is O(n), and the time complexity of the other operations is O(1). Here, n is the length of compressedString.
class StringIterator:
def __init__(self, compressedString: str):
self.d = []
self.p = 0
n = len(compressedString)
i = 0
while i < n:
c = compressedString[i]
x = 0
i += 1
while i < n and compressedString[i].isdigit():
x = x * 10 + int(compressedString[i])
i += 1
self.d.append([c, x])
def next(self) -> str:
if not self.hasNext():
return ' '
ans = self.d[self.p][0]
self.d[self.p][1] -= 1
if self.d[self.p][1] == 0:
self.p += 1
return ans
def hasNext(self) -> bool:
return self.p < len(self.d) and self.d[self.p][1] > 0
# Your StringIterator object will be instantiated and called as such:
# obj = StringIterator(compressedString)
# param_1 = obj.next()
# param_2 = obj.hasNext()class StringIterator {
private List<Node> d = new ArrayList<>();
private int p;
public StringIterator(String compressedString) {
int n = compressedString.length();
int i = 0;
while (i < n) {
char c = compressedString.charAt(i);
int x = 0;
while (++i < n && Character.isDigit(compressedString.charAt(i))) {
x = x * 10 + (compressedString.charAt(i) - '0');
}
d.add(new Node(c, x));
}
}
public char next() {
if (!hasNext()) {
return ' ';
}
char ans = d.get(p).c;
if (--d.get(p).x == 0) {
++p;
}
return ans;
}
public boolean hasNext() {
return p < d.size() && d.get(p).x > 0;
}
}
class Node {
char c;
int x;
Node(char c, int x) {
this.c = c;
this.x = x;
}
}
/**
* Your StringIterator object will be instantiated and called as such:
* StringIterator obj = new StringIterator(compressedString);
* char param_1 = obj.next();
* boolean param_2 = obj.hasNext();
*/class StringIterator {
public:
StringIterator(string compressedString) {
int n = compressedString.size();
int i = 0;
while (i < n) {
char c = compressedString[i];
int x = 0;
while (++i < n && isdigit(compressedString[i])) {
x = x * 10 + (compressedString[i] - '0');
}
d.push_back({c, x});
}
}
char next() {
if (!hasNext()) return ' ';
char ans = d[p].first;
if (--d[p].second == 0) {
++p;
}
return ans;
}
bool hasNext() {
return p < d.size() && d[p].second > 0;
}
private:
vector<pair<char, int>> d;
int p = 0;
};
/**
* Your StringIterator object will be instantiated and called as such:
* StringIterator* obj = new StringIterator(compressedString);
* char param_1 = obj->next();
* bool param_2 = obj->hasNext();
*/class StringIterator {
private d: [string, number][] = [];
private p: number = 0;
constructor(compressedString: string) {
const n = compressedString.length;
let i = 0;
while (i < n) {
const c = compressedString[i];
let x = 0;
i++;
while (i < n && !isNaN(Number(compressedString[i]))) {
x = x * 10 + Number(compressedString[i]);
i++;
}
this.d.push([c, x]);
}
}
next(): string {
if (!this.hasNext()) {
return ' ';
}
const ans = this.d[this.p][0];
this.d[this.p][1]--;
if (this.d[this.p][1] === 0) {
this.p++;
}
return ans;
}
hasNext(): boolean {
return this.p < this.d.length && this.d[this.p][1] > 0;
}
}
/**
* Your StringIterator object will be instantiated and called as such:
* var obj = new StringIterator(compressedString)
* var param_1 = obj.next()
* var param_2 = obj.hasNext()
*/type pair struct {
c byte
x int
}
type StringIterator struct {
d []pair
p int
}
func Constructor(compressedString string) StringIterator {
n := len(compressedString)
i := 0
d := []pair{}
for i < n {
c := compressedString[i]
x := 0
i++
for i < n && compressedString[i] >= '0' && compressedString[i] <= '9' {
x = x*10 + int(compressedString[i]-'0')
i++
}
d = append(d, pair{c, x})
}
return StringIterator{d, 0}
}
func (this *StringIterator) Next() byte {
if !this.HasNext() {
return ' '
}
ans := this.d[this.p].c
this.d[this.p].x--
if this.d[this.p].x == 0 {
this.p++
}
return ans
}
func (this *StringIterator) HasNext() bool {
return this.p < len(this.d) && this.d[this.p].x > 0
}
/**
* Your StringIterator object will be instantiated and called as such:
* obj := Constructor(compressedString);
* param_1 := obj.Next();
* param_2 := obj.HasNext();
*/Complexity analysis
| Approach | Time | Space | When to Use |
|---|---|---|---|
| Fully Decompress String | O(n + totalExpanded) | O(totalExpanded) | Simple implementation when counts are small and memory is not a concern |
| Parse and Store Character Counts | O(n) preprocessing, O(1) per operation | O(k) | Preferred approach for large counts or memory-constrained scenarios |
Frequently asked questions
Is Design Compressed String Iterator easy or hard?
Design Compressed String Iterator is rated Easy on LeetCode but includes basic system design elements. The challenge is parsing multi-digit counts correctly and maintaining iterator state efficiently without expanding the full string.
Design Compressed String Iterator Python/Java solution
Python and Java solutions typically parse the string during initialization and store character-count pairs in a list or arrays. next() returns the current character and decreases the remaining count, while hasNext() checks whether any counts remain. The logic stays identical across Python, Java, C++, Go, and TypeScript.
How to solve Design Compressed String Iterator in O(n)?
Scan the compressed string once and extract characters with their numeric counts. Store them in arrays or a list of pairs. Maintain a pointer to the current segment and decrease its remaining count whenever next() is called, advancing the pointer when the count becomes zero.
What is the best approach for Design Compressed String Iterator?
The best approach parses the compressed string into (character, count) pairs and tracks remaining counts during iteration. Each next() call returns the current character and decreases its count, moving to the next pair when the count reaches zero. This keeps operations O(1) while using only O(k) space where k is the number of compressed segments.
Is Design Compressed String Iterator asked at Google/Amazon/Meta?
Iterator design and run-length encoding problems appear frequently in interviews at large tech companies including Google, Amazon, and Meta. Variations often test object-oriented design, efficient parsing, and maintaining state across method calls.
What data structure is used in Design Compressed String Iterator?
Most implementations store parsed results in an array or list of (character, count) pairs. An index pointer and a remaining-count variable track the iterator state. This structure supports constant-time updates during iteration.
What is the time complexity of Design Compressed String Iterator?
Parsing the compressed input takes O(n) time where n is the length of the compressed string. After initialization, both next() and hasNext() run in O(1) time because they only update counters and indexes. Space complexity is O(k) for storing character-count pairs.
