Facebook Pixel

900. RLE Iterator

MediumDesignArrayCountingIterator
Leetcode Link

Problem Description

This problem asks you to implement an iterator for a run-length encoded (RLE) array. Run-length encoding is a compression technique where consecutive repeated values are stored as a count-value pair.

In the RLE format used here:

  • The encoding array has even length
  • At every even index i, encoding[i] represents the count (how many times a value appears)
  • At every odd index i+1, encoding[i+1] represents the actual value being repeated

For example, the sequence [8,8,8,5,5] can be encoded as [3,8,2,5], meaning "3 occurrences of 8, followed by 2 occurrences of 5".

You need to implement a class RLEIterator with two methods:

  1. Constructor RLEIterator(int[] encoded): Initializes the iterator with the run-length encoded array.

  2. Method next(int n): This method "exhausts" (consumes) the next n elements from the decoded sequence and returns the last element that was exhausted. If there aren't enough elements left to exhaust n elements, it returns -1.

The key challenge is to efficiently traverse through the encoded array without actually decoding it into the full sequence. When next(n) is called, you need to:

  • Skip through n elements in the logical decoded sequence
  • Keep track of your position within the encoded array
  • Return the value of the nth element you exhausted, or -1 if you run out of elements

For instance, if the encoding is [3,8,2,5] and you call next(2), you exhaust 2 elements from the sequence (both are 8s), so you return 8. If you then call next(1), you exhaust one more 8, returning 8. If you call next(2) again, you exhaust the last two 5s, returning 5.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is that we don't need to actually decode the entire RLE array into its full sequence. Instead, we can navigate through the encoded array directly by keeping track of our position within it.

Think of the encoded array as a series of "blocks" where each block contains multiple copies of the same value. For example, [3,8,2,5] represents two blocks: a block of 3 eights and a block of 2 fives.

When we need to exhaust n elements, we're essentially "consuming" elements from these blocks. The challenge is: what if n is larger than the current block? We'd need to consume the entire current block and move to the next one, continuing until we've exhausted n elements total.

This leads us to use two pointers:

  • Pointer i tracks which block we're currently in (it points to the count in the encoding array)
  • Pointer j tracks how many elements we've already consumed from the current block

When next(n) is called, we need to handle two cases:

  1. Current block has fewer remaining elements than n: If encoding[i] - j < n, we consume all remaining elements in this block (which is encoding[i] - j elements), reduce n by that amount, and move to the next block by advancing i by 2 and resetting j to 0.

  2. Current block has enough elements: If encoding[i] - j >= n, we can satisfy the request entirely from the current block. We advance j by n and return the value at encoding[i + 1].

We keep repeating case 1 until we either satisfy the request (case 2) or run out of blocks entirely (return -1).

This approach is efficient because we're jumping through blocks rather than iterating through individual elements, giving us a time complexity proportional to the number of blocks rather than the total number of elements in the decoded sequence.

Solution Approach

The solution uses a two-pointer approach to efficiently traverse the run-length encoded array without decoding it.

Data Structure:

  • self.encoding: Stores the run-length encoded array
  • self.i: Points to the current count position in the encoding (always at even indices)
  • self.j: Tracks how many elements have been consumed from the current block

Implementation Details:

  1. Initialization (__init__ method):

    • Store the encoding array
    • Initialize i = 0 to point to the first count
    • Initialize j = 0 to indicate no elements consumed from the first block
  2. The next(n) method:

    The method uses a while loop to process blocks until we exhaust n elements:

    while self.i < len(self.encoding):

    Inside the loop, we check if the current block has enough remaining elements:

    • Case 1: Current block insufficient (self.encoding[self.i] - self.j < n):
      • Calculate remaining elements in current block: self.encoding[self.i] - self.j
      • Subtract these from n: n -= self.encoding[self.i] - self.j
      • Move to next block: self.i += 2 (skip both count and value)
      • Reset consumption counter: self.j = 0
      • Continue to next iteration
    • Case 2: Current block sufficient (self.encoding[self.i] - self.j >= n):
      • Consume n elements from current block: self.j += n
      • Return the value of current block: return self.encoding[self.i + 1]

    If the loop completes without returning (meaning we've exhausted all blocks), return -1.

Example Walkthrough:

Given encoding [3,8,2,5]:

  • next(2): i=0, j=0. Block has 3 elements, need 2. Update j=2, return 8
  • next(1): i=0, j=2. Block has 1 remaining, need 1. Update j=3, return 8
  • next(2): i=0, j=3. Block has 0 remaining, need 2. Move to next block (i=2, j=0), then consume 2 from new block, return 5

Time Complexity: O(k) per call where k is the number of blocks we need to traverse Space Complexity: O(1) as we only maintain two pointers regardless of input size

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a concrete example with the encoding [3,8,2,5,1,4] which represents the sequence: [8,8,8,5,5,4].

Initial State:

  • encoding = [3,8,2,5,1,4]
  • i = 0 (pointing to count 3)
  • j = 0 (no elements consumed from current block)

Call 1: next(2)

  • Current block: 3 eights, with 0 consumed → 3 remaining
  • Need to exhaust: 2 elements
  • Since 3 >= 2, we can satisfy this from the current block
  • Update j = 0 + 2 = 2
  • Return encoding[1] = 8
  • State after: i=0, j=2

Call 2: next(3)

  • Current block: 3 eights, with 2 consumed → 1 remaining
  • Need to exhaust: 3 elements
  • Since 1 < 3, current block insufficient
    • Exhaust remaining 1 element: n = 3 - 1 = 2
    • Move to next block: i = 0 + 2 = 2, j = 0
  • Now at block of 2 fives
  • Since 2 >= 2, we can satisfy remaining from this block
  • Update j = 0 + 2 = 2
  • Return encoding[3] = 5
  • State after: i=2, j=2

Call 3: next(2)

  • Current block: 2 fives, with 2 consumed → 0 remaining
  • Need to exhaust: 2 elements
  • Since 0 < 2, current block insufficient
    • Exhaust remaining 0 elements: n = 2 - 0 = 2
    • Move to next block: i = 2 + 2 = 4, j = 0
  • Now at block of 1 four
  • Since 1 < 2, current block still insufficient
    • Exhaust remaining 1 element: n = 2 - 1 = 1
    • Move to next block: i = 4 + 2 = 6, j = 0
  • Now i = 6 >= len(encoding), we've run out of elements
  • Return -1

This example demonstrates all key scenarios: consuming within a block, spanning multiple blocks, and running out of elements.

Solution Implementation

1from typing import List
2
3
4class RLEIterator:
5    def __init__(self, encoding: List[int]) -> None:
6        """
7        Initialize the RLE iterator with an encoded array.
8      
9        Args:
10            encoding: Run-length encoded array where encoding[i] is the count
11                     and encoding[i+1] is the value for each pair.
12        """
13        self.encoding = encoding
14        self.current_index = 0  # Points to the current count in encoding
15        self.consumed_count = 0  # Number of elements consumed from current run
16  
17    def next(self, n: int) -> int:
18        """
19        Exhaust the next n elements and return the last element exhausted.
20      
21        Args:
22            n: Number of elements to exhaust
23          
24        Returns:
25            The value of the last exhausted element, or -1 if there are fewer
26            than n elements remaining.
27        """
28        while self.current_index < len(self.encoding):
29            # Get the available count in the current run
30            available = self.encoding[self.current_index] - self.consumed_count
31          
32            if available < n:
33                # Current run doesn't have enough elements
34                # Consume all remaining elements in this run
35                n -= available
36                self.current_index += 2  # Move to next count-value pair
37                self.consumed_count = 0  # Reset consumed count for new run
38            else:
39                # Current run has enough elements
40                # Consume n elements and return the value
41                self.consumed_count += n
42                return self.encoding[self.current_index + 1]
43      
44        # Not enough elements remaining in the entire encoding
45        return -1
46
47
48# Your RLEIterator object will be instantiated and called as such:
49# obj = RLEIterator(encoding)
50# param_1 = obj.next(n)
51
1class RLEIterator {
2    // Array storing the run-length encoded sequence
3    // Format: [count1, value1, count2, value2, ...]
4    private int[] encoding;
5  
6    // Current index pointing to the count in the encoding array (increments by 2)
7    private int currentIndex;
8  
9    // Number of elements already consumed from the current run at encoding[currentIndex]
10    private int consumedInCurrentRun;
11
12    /**
13     * Initializes the RLE Iterator with a run-length encoded array.
14     * @param encoding The run-length encoded array where encoding[i] is the count
15     *                 and encoding[i+1] is the value for each run
16     */
17    public RLEIterator(int[] encoding) {
18        this.encoding = encoding;
19        this.currentIndex = 0;
20        this.consumedInCurrentRun = 0;
21    }
22
23    /**
24     * Exhausts the next n elements from the iterator and returns the last element exhausted.
25     * @param n The number of elements to exhaust
26     * @return The last element exhausted, or -1 if there are fewer than n elements remaining
27     */
28    public int next(int n) {
29        // Iterate through the encoding array to consume n elements
30        while (currentIndex < encoding.length) {
31            // Calculate remaining elements in the current run
32            int remainingInCurrentRun = encoding[currentIndex] - consumedInCurrentRun;
33          
34            if (remainingInCurrentRun < n) {
35                // Current run doesn't have enough elements, consume all remaining
36                n -= remainingInCurrentRun;
37                // Move to the next run (skip count and value)
38                currentIndex += 2;
39                // Reset consumed counter for the new run
40                consumedInCurrentRun = 0;
41            } else {
42                // Current run has enough elements to satisfy the request
43                consumedInCurrentRun += n;
44                // Return the value of the current run
45                return encoding[currentIndex + 1];
46            }
47        }
48      
49        // Not enough elements in the entire sequence
50        return -1;
51    }
52}
53
54/**
55 * Your RLEIterator object will be instantiated and called as such:
56 * RLEIterator obj = new RLEIterator(encoding);
57 * int param_1 = obj.next(n);
58 */
59
1class RLEIterator {
2public:
3    /**
4     * Constructor: Initialize the RLE iterator with the given encoding
5     * @param encoding: Run-length encoded array where encoding[i] is the count 
6     *                  and encoding[i+1] is the value
7     */
8    RLEIterator(vector<int>& encoding) {
9        this->encoding_ = encoding;
10    }
11
12    /**
13     * Exhausts the next n elements from the iterator and returns the last element exhausted
14     * @param n: Number of elements to exhaust
15     * @return: The last element exhausted, or -1 if there are fewer than n elements left
16     */
17    int next(int n) {
18        // Iterate through the encoding array
19        while (current_index_ < encoding_.size()) {
20            // Calculate remaining elements in current run
21            int remaining_in_current_run = encoding_[current_index_] - consumed_from_current_run_;
22          
23            if (remaining_in_current_run < n) {
24                // Current run doesn't have enough elements, consume all remaining and move to next run
25                n -= remaining_in_current_run;
26                current_index_ += 2;  // Move to next count-value pair
27                consumed_from_current_run_ = 0;  // Reset consumption counter for new run
28            } else {
29                // Current run has enough elements to satisfy the request
30                consumed_from_current_run_ += n;
31                return encoding_[current_index_ + 1];  // Return the value of current run
32            }
33        }
34      
35        // No more elements available
36        return -1;
37    }
38
39private:
40    vector<int> encoding_;                // Store the run-length encoded array
41    int current_index_ = 0;               // Index pointing to current count in encoding array
42    int consumed_from_current_run_ = 0;   // Number of elements already consumed from current run
43};
44
45/**
46 * Your RLEIterator object will be instantiated and called as such:
47 * RLEIterator* obj = new RLEIterator(encoding);
48 * int param_1 = obj->next(n);
49 */
50
1// Global variables to store the RLE encoding state
2let encoding: number[] = [];
3let currentIndex: number = 0;  // Index pointing to current count in encoding array
4let consumedFromCurrent: number = 0;  // Number of elements consumed from current run
5
6/**
7 * Initializes the RLE iterator with the given encoding
8 * @param encodingInput - Array where even indices contain counts and odd indices contain values
9 *                        Format: [count1, value1, count2, value2, ...]
10 */
11function initializeRLEIterator(encodingInput: number[]): void {
12    encoding = encodingInput;
13    currentIndex = 0;
14    consumedFromCurrent = 0;
15}
16
17/**
18 * Exhausts the next n elements from the run-length encoded sequence
19 * @param n - Number of elements to exhaust
20 * @returns The value of the last exhausted element, or -1 if fewer than n elements remain
21 */
22function next(n: number): number {
23    // Iterate through the encoding array to consume n elements
24    while (currentIndex < encoding.length) {
25        // Calculate remaining elements in current run
26        const remainingInCurrentRun = encoding[currentIndex] - consumedFromCurrent;
27      
28        if (remainingInCurrentRun < n) {
29            // Current run doesn't have enough elements, consume all remaining and move to next run
30            n -= remainingInCurrentRun;
31            currentIndex += 2;  // Move to next count-value pair
32            consumedFromCurrent = 0;  // Reset consumption counter for new run
33        } else {
34            // Current run has enough elements to satisfy the request
35            consumedFromCurrent += n;
36            return encoding[currentIndex + 1];  // Return the value at odd index
37        }
38    }
39  
40    // No more elements available in the sequence
41    return -1;
42}
43

Time and Space Complexity

Time Complexity: O(n + q) where n is the length of the run-length encoding array and q is the total number of times next(n) is called.

The initialization takes O(1) time. For the next(n) method, in the worst case, we might traverse through the entire encoding array across all calls to next(). Each element in the encoding array is visited at most once throughout all calls (as we move forward with index i and never go back). Since the encoding has length n, and we make q calls to next(), the total time complexity is O(n + q).

Space Complexity: O(n) where n is the length of the run-length encoding array.

We store the encoding array which takes O(n) space. The additional variables i and j are just integer pointers that take O(1) space. Therefore, the overall space complexity is O(n).

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Incorrect Index Management When Moving to Next Block

Pitfall: A common mistake is incrementing the index by 1 instead of 2 when moving to the next count-value pair, or forgetting to reset the consumed count.

# WRONG - Only increments by 1
if available < n:
    n -= available
    self.current_index += 1  # Should be += 2
    # Forgot to reset consumed_count

Solution: Always remember that the encoding array stores pairs, so you need to skip both the count and value:

# CORRECT
if available < n:
    n -= available
    self.current_index += 2  # Skip both count and value
    self.consumed_count = 0  # Reset for new block

2. Handling Zero-Count Entries

Pitfall: The encoding might contain entries with count 0 (e.g., [0,5,3,8]). If not handled properly, this can cause infinite loops or incorrect results.

# PROBLEMATIC - Can get stuck if encoding[i] is 0
while self.current_index < len(self.encoding):
    available = self.encoding[self.current_index] - self.consumed_count
    if available < n:
        n -= available  # If available is 0, n never decreases
        # ... rest of code

Solution: Skip zero-count entries immediately:

def next(self, n: int) -> int:
    while self.current_index < len(self.encoding):
        # Skip zero-count entries
        if self.encoding[self.current_index] == 0:
            self.current_index += 2
            continue
          
        available = self.encoding[self.current_index] - self.consumed_count
        # ... rest of logic

3. Not Preserving State Between Calls

Pitfall: Resetting the iterator state in each next() call or using local variables instead of instance variables.

# WRONG - Loses state between calls
def next(self, n: int) -> int:
    current_index = 0  # Should use self.current_index
    consumed_count = 0  # Should use self.consumed_count
    # ... rest of code

Solution: Use instance variables to maintain state across multiple next() calls:

# CORRECT
def __init__(self, encoding: List[int]) -> None:
    self.encoding = encoding
    self.current_index = 0  # Instance variable
    self.consumed_count = 0  # Instance variable

4. Off-by-One Error in Available Count Calculation

Pitfall: Incorrectly calculating how many elements are available in the current block.

# WRONG - Might use wrong formula
available = self.encoding[self.current_index] - self.consumed_count - 1
# or
available = self.encoding[self.current_index + 1] - self.consumed_count

Solution: The correct formula is straightforward:

# CORRECT
available = self.encoding[self.current_index] - self.consumed_count

5. Returning Wrong Value When Block Partially Consumed

Pitfall: When exhausting elements from a partially consumed block, accidentally returning the count instead of the value.

# WRONG - Returns count instead of value
if available >= n:
    self.consumed_count += n
    return self.encoding[self.current_index]  # Should be self.current_index + 1

Solution: Always return the value at the odd index (current_index + 1):

# CORRECT
if available >= n:
    self.consumed_count += n
    return self.encoding[self.current_index + 1]  # Value is at odd index
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of the following uses divide and conquer strategy?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More