Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

900. RLE Iterator - Leetcode Solution

Code Implementation

class RLEIterator:

    def __init__(self, encoding):
        self.encoding = encoding
        self.index = 0  # Points to the count in the encoding array

    def next(self, n):
        while self.index < len(self.encoding):
            count = self.encoding[self.index]
            value = self.encoding[self.index + 1]
            if n > count:
                n -= count
                self.index += 2
            else:
                self.encoding[self.index] -= n
                return value
        return -1
      
class RLEIterator {
public:
    vector<int> encoding;
    int index;

    RLEIterator(vector<int>& encoding) {
        this->encoding = encoding;
        index = 0;
    }
    
    int next(int n) {
        while (index < encoding.size()) {
            if (n > encoding[index]) {
                n -= encoding[index];
                index += 2;
            } else {
                encoding[index] -= n;
                return encoding[index + 1];
            }
        }
        return -1;
    }
};
      
class RLEIterator {
    private int[] encoding;
    private int index;

    public RLEIterator(int[] encoding) {
        this.encoding = encoding;
        this.index = 0;
    }
    
    public int next(int n) {
        while (index < encoding.length) {
            if (n > encoding[index]) {
                n -= encoding[index];
                index += 2;
            } else {
                encoding[index] -= n;
                return encoding[index + 1];
            }
        }
        return -1;
    }
}
      
var RLEIterator = function(encoding) {
    this.encoding = encoding;
    this.index = 0;
};

RLEIterator.prototype.next = function(n) {
    while (this.index < this.encoding.length) {
        if (n > this.encoding[this.index]) {
            n -= this.encoding[this.index];
            this.index += 2;
        } else {
            this.encoding[this.index] -= n;
            return this.encoding[this.index + 1];
        }
    }
    return -1;
};
      

Problem Description

The RLE Iterator problem asks you to implement a class that can efficiently iterate through a run-length encoded sequence. The input is an array encoding where the even-indexed elements represent counts and the odd-indexed elements represent values. For example, [3,8,0,9,2,5] means "three 8s, zero 9s, two 5s" (i.e., the expanded sequence would be [8,8,8,5,5]).

You must implement two methods:

  • __init__(encoding): Initialize the iterator with the run-length encoding array.
  • next(n): Consume n elements from the sequence and return the last element consumed. If there are not enough elements left, return -1.
Key constraints:
  • Each call to next(n) must consume elements in order and not reuse any element.
  • Once an element is consumed, it cannot be returned again in future calls.
  • There is only one valid way to consume the sequence according to the encoding.

Thought Process

The first thing to notice is that expanding the entire sequence into a list would be inefficient, especially if some counts are very large. For example, if the encoding is [1000000, 1], expanding this would take up a lot of memory and time.

Instead, we want a way to "skip" over large runs without generating all the elements. The iterator should efficiently move through the encoding array, keeping track of how many elements have been consumed from each run.

The brute-force solution would be to expand the encoded sequence into a full array and consume elements from it as needed. But this is not scalable for large inputs. The optimized approach is to process the encoding array directly, using pointers or indices to track our position and remaining counts.

Solution Approach

To solve this problem efficiently, we avoid expanding the sequence and instead "walk" through the encoding as needed.

  1. Initialization: Store the encoding array and keep an index pointer (let's call it index) at the start (position 0).
  2. Processing next(n):
    • While n is larger than the current count at encoding[index], subtract that count from n and move the pointer forward by 2 (to the next run).
    • If n is less than or equal to the current count, subtract n from the count at encoding[index] and return the value at encoding[index + 1].
    • If you reach the end of the array before consuming n elements, return -1.
  3. State Update: Each time you consume elements, update the count in the encoding array so that future calls know how many are left.

This approach is efficient because it only iterates through the encoding array as needed, without ever constructing the full sequence.

Example Walkthrough

Let's use the input encoding = [3,8,0,9,2,5] and several calls to next():

  • First call: next(2)
    - The first run is 3 of 8. We have enough to consume 2.
    - Subtract 2 from the count (now 1 left), return 8.
  • Second call: next(1)
    - 1 left in the first run (8). Consume it, count becomes 0, return 8.
  • Third call: next(1)
    - First run is exhausted, move to next run (0, 9), but count is 0.
    - Move to next run (2, 5). We can consume 1.
    - Subtract 1 from count (now 1 left), return 5.
  • Fourth call: next(2)
    - Only 1 left in the current run (5). Need 2, but only 1 available.
    - Consume the last 1, move past the end of the encoding.
    - Not enough elements to fulfill the request, return -1.

After each call, the encoding array's counts are updated to reflect what has already been consumed.

Time and Space Complexity

  • Brute-force Approach:
    • Time: O(total number of elements in the expanded sequence) per initialization, O(1) per next call.
    • Space: O(total number of elements in the expanded sequence).
  • Optimized Approach (our solution):
    • Time: O(1) amortized per next call, but in worst case O(k) where k is the number of runs (when skipping many exhausted runs).
    • Space: O(1) extra space, plus O(N) for the encoding array itself (no expansion).

The optimized approach is far superior for large inputs, as it never constructs the full sequence and only traverses the encoding as needed.

Summary

The RLE Iterator problem demonstrates how to work efficiently with compressed data representations. By tracking your position and remaining counts within the encoding array, you avoid unnecessary computation and memory use. The key insight is to process the encoding in place, skipping over exhausted runs and updating counts as you go. This approach is both space- and time-efficient, making it ideal for large or highly compressed sequences.