Facebook Pixel

2526. Find Consecutive Integers from a Data Stream

MediumDesignQueueHash TableCountingData Stream
Leetcode Link

Problem Description

You need to design a data structure that monitors a stream of integers and checks whether the most recent k integers are all equal to a specific target value.

The DataStream class should support two operations:

  1. Constructor DataStream(int value, int k): Initializes the data structure with:

    • value: the target integer that we want to check for
    • k: the number of consecutive integers we need to examine
    • An initially empty stream of integers
  2. Method consec(int num):

    • Adds a new integer num to the stream
    • Returns true if the last k integers in the stream are ALL equal to value
    • Returns false if:
      • Any of the last k integers is different from value, OR
      • There are fewer than k integers in the stream so far

The solution uses a counting approach with a counter cnt that tracks how many consecutive integers equal to value we've seen most recently. When a new number arrives:

  • If the number equals value, increment the counter
  • If the number is different from value, reset the counter to 0
  • Return whether the counter is at least k

This approach is efficient because it only needs to track one counter instead of storing the entire stream or the last k elements.

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

Intuition

The key insight is recognizing that we don't actually need to store the last k integers from the stream. Instead, we only care about one specific property: are ALL of the last k integers equal to value?

Think about what happens as we process the stream:

  • If we encounter a number that's not equal to value, we know for certain that the last k integers cannot all be equal to value (at least not until we've seen k more integers that are all equal to value)
  • If we encounter a number equal to value, we're building up a consecutive sequence

This observation leads us to realize we only need to track how many consecutive occurrences of value we've seen most recently. It's like keeping a "streak counter":

  • When we see value, we extend our current streak
  • When we see any other number, our streak breaks and resets to 0
  • We have a valid sequence when our streak reaches or exceeds k

For example, if value = 5 and k = 3, and we receive the stream [5, 5, 4, 5, 5, 5, 5]:

  • After [5, 5]: counter = 2 (not enough yet, return false)
  • After [5, 5, 4]: counter = 0 (streak broken, return false)
  • After [5, 5, 4, 5]: counter = 1 (new streak started, return false)
  • After [5, 5, 4, 5, 5]: counter = 2 (building up, return false)
  • After [5, 5, 4, 5, 5, 5]: counter = 3 (we have 3 consecutive 5s, return true)
  • After [5, 5, 4, 5, 5, 5, 5]: counter = 4 (still valid, return true)

This approach is elegant because it uses O(1) space regardless of the stream size or the value of k.

Learn more about Queue and Data Stream patterns.

Solution Approach

The implementation uses a simple counting strategy with just two instance variables to track the state:

  1. Instance Variables:

    • self.val: Stores the target value we're checking for
    • self.k: Stores the required number of consecutive occurrences
    • self.cnt: Counter to track the current streak of consecutive values equal to self.val
  2. Constructor (__init__):

    def __init__(self, value: int, k: int):
        self.val, self.k = value, k
        self.cnt = 0
    • Initializes the target value and required count
    • Sets the counter to 0 since we haven't seen any numbers yet
  3. The consec Method:

    def consec(self, num: int) -> bool:
        self.cnt = 0 if num != self.val else self.cnt + 1
        return self.cnt >= self.k

    This method performs two key operations:

    Step 1: Update the counter

    • If num != self.val: Reset self.cnt to 0 (streak is broken)
    • If num == self.val: Increment self.cnt by 1 (extend the streak)

    This is elegantly written as a conditional expression:

    self.cnt = 0 if num != self.val else self.cnt + 1

    Step 2: Check the condition

    • Return True if self.cnt >= self.k (we have at least k consecutive values)
    • Return False otherwise

Time and Space Complexity:

  • Time Complexity: O(1) for each consec call - we only perform constant-time operations
  • Space Complexity: O(1) - we only store three variables regardless of the stream size

The beauty of this solution is its simplicity. By recognizing that we only need to track the current consecutive count rather than storing actual values, we achieve both optimal time and space efficiency.

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 value = 3 and k = 2. We want to check if the last 2 integers are both equal to 3.

Initial State:

  • val = 3, k = 2, cnt = 0

Stream: [3, 3, 1, 3, 3]

Step 1: consec(3) is called

  • Check: num (3) == val (3)? Yes
  • Update: cnt = cnt + 1 = 0 + 1 = 1
  • Check: cnt (1) >= k (2)? No
  • Return: false (we've only seen one 3 so far)

Step 2: consec(3) is called

  • Check: num (3) == val (3)? Yes
  • Update: cnt = cnt + 1 = 1 + 1 = 2
  • Check: cnt (2) >= k (2)? Yes
  • Return: true (the last 2 integers are both 3)

Step 3: consec(1) is called

  • Check: num (1) == val (3)? No
  • Update: cnt = 0 (streak broken)
  • Check: cnt (0) >= k (2)? No
  • Return: false (the last 2 integers are [3, 1], not both 3)

Step 4: consec(3) is called

  • Check: num (3) == val (3)? Yes
  • Update: cnt = cnt + 1 = 0 + 1 = 1
  • Check: cnt (1) >= k (2)? No
  • Return: false (the last 2 integers are [1, 3], not both 3)

Step 5: consec(3) is called

  • Check: num (3) == val (3)? Yes
  • Update: cnt = cnt + 1 = 1 + 1 = 2
  • Check: cnt (2) >= k (2)? Yes
  • Return: true (the last 2 integers are [3, 3], both equal to 3)

The key insight is that we don't need to store the actual stream values. The counter cnt tells us everything we need: when it reaches k, we know the last k values must all be equal to our target value. When we see a different number, resetting to 0 ensures we start counting fresh consecutive occurrences.

Solution Implementation

1class DataStream:
2    def __init__(self, value: int, k: int):
3        """
4        Initialize the DataStream with a target value and minimum consecutive count.
5      
6        Args:
7            value: The target value to track consecutive occurrences
8            k: The minimum number of consecutive occurrences required
9        """
10        self.target_value = value
11        self.min_consecutive = k
12        self.consecutive_count = 0
13
14    def consec(self, num: int) -> bool:
15        """
16        Process a new number in the stream and check if we have at least k consecutive target values.
17      
18        Args:
19            num: The new number in the data stream
20          
21        Returns:
22            True if the last k values (including current) are all equal to target_value, False otherwise
23        """
24        # Reset counter if current number doesn't match target value
25        if num != self.target_value:
26            self.consecutive_count = 0
27        else:
28            # Increment counter for matching value
29            self.consecutive_count += 1
30      
31        # Check if we have at least k consecutive target values
32        return self.consecutive_count >= self.min_consecutive
33
34
35# Your DataStream object will be instantiated and called as such:
36# obj = DataStream(value, k)
37# param_1 = obj.consec(num)
38
1/**
2 * A data stream class that tracks consecutive occurrences of a target value.
3 * Used to determine if the last k elements in the stream are all equal to the target value.
4 */
5class DataStream {
6    // Counter to track consecutive occurrences of the target value
7    private int consecutiveCount;
8  
9    // The target value to track in the stream
10    private int targetValue;
11  
12    // The minimum number of consecutive occurrences required
13    private int requiredCount;
14
15    /**
16     * Initializes the DataStream with a target value and required consecutive count.
17     * @param value The target value to track in the stream
18     * @param k The minimum number of consecutive occurrences needed
19     */
20    public DataStream(int value, int k) {
21        this.targetValue = value;
22        this.requiredCount = k;
23        this.consecutiveCount = 0;
24    }
25
26    /**
27     * Adds a new number to the stream and checks if the last k elements are all equal to the target value.
28     * @param num The new number being added to the stream
29     * @return true if the last k or more elements are all equal to the target value, false otherwise
30     */
31    public boolean consec(int num) {
32        // If the current number matches the target value, increment the counter
33        // Otherwise, reset the counter to 0
34        consecutiveCount = (num == targetValue) ? consecutiveCount + 1 : 0;
35      
36        // Check if we have at least k consecutive occurrences of the target value
37        return consecutiveCount >= requiredCount;
38    }
39}
40
41/**
42 * Your DataStream object will be instantiated and called as such:
43 * DataStream obj = new DataStream(value, k);
44 * boolean param_1 = obj.consec(num);
45 */
46
1class DataStream {
2public:
3    /**
4     * Initializes the data stream tracker with target value and required consecutive count
5     * @param value - The target value to track consecutive occurrences
6     * @param k - The minimum number of consecutive occurrences required
7     */
8    DataStream(int value, int k) {
9        targetValue = value;
10        requiredCount = k;
11    }
12
13    /**
14     * Processes a new number in the stream and checks if we have k consecutive target values
15     * @param num - The new number from the data stream
16     * @return true if the last k values (including current) equal targetValue, false otherwise
17     */
18    bool consec(int num) {
19        // Reset counter if current number doesn't match target, otherwise increment
20        consecutiveCount = (num == targetValue) ? consecutiveCount + 1 : 0;
21      
22        // Check if we've seen at least k consecutive target values
23        return consecutiveCount >= requiredCount;
24    }
25
26private:
27    int consecutiveCount = 0;  // Tracks current consecutive occurrences of targetValue
28    int targetValue;            // The value we're tracking for consecutive occurrences
29    int requiredCount;          // Minimum consecutive occurrences needed to return true
30};
31
32/**
33 * Your DataStream object will be instantiated and called as such:
34 * DataStream* obj = new DataStream(value, k);
35 * bool param_1 = obj->consec(num);
36 */
37
1// Target value to check against incoming stream
2let targetValue: number;
3// Minimum consecutive occurrences required
4let requiredConsecutive: number;
5// Current count of consecutive matches
6let consecutiveCount: number;
7
8/**
9 * Initializes the data stream checker with a target value and minimum consecutive requirement
10 * @param value - The target value to match in the stream
11 * @param k - The minimum number of consecutive occurrences needed
12 */
13function DataStream(value: number, k: number): void {
14    targetValue = value;
15    requiredConsecutive = k;
16    consecutiveCount = 0;
17}
18
19/**
20 * Checks if the last k values in the stream are all equal to the target value
21 * @param num - The new number added to the stream
22 * @returns true if the last k or more values equal the target value, false otherwise
23 */
24function consec(num: number): boolean {
25    // Reset counter if current number doesn't match target, otherwise increment
26    consecutiveCount = targetValue === num ? consecutiveCount + 1 : 0;
27    // Check if we have at least k consecutive matches
28    return consecutiveCount >= requiredConsecutive;
29}
30

Time and Space Complexity

Time Complexity: O(1)

The consec method performs a constant number of operations:

  • One comparison (num != self.val)
  • One conditional assignment (either setting self.cnt = 0 or self.cnt = self.cnt + 1)
  • One comparison (self.cnt >= self.k)
  • One return statement

All these operations take constant time regardless of the input size or the values of k and value. The method doesn't contain any loops or recursive calls, so each call to consec executes in constant time.

Space Complexity: O(1)

The DataStream class uses a fixed amount of memory:

  • Three instance variables: self.val, self.k, and self.cnt
  • Each variable stores a single integer value

The space usage doesn't grow with the number of calls to consec or with the size of the input parameters. The class maintains only these three variables throughout its lifetime, using constant space.

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

Common Pitfalls

Pitfall 1: Incorrect Counter Reset Logic

The Mistake: A common error is resetting the counter after checking the condition, or implementing the reset logic incorrectly:

def consec(self, num: int) -> bool:
    if num == self.target_value:
        self.consecutive_count += 1
    result = self.consecutive_count >= self.min_consecutive
    if num != self.target_value:
        self.consecutive_count = 0  # Reset AFTER checking - WRONG!
    return result

This creates a bug where if we have exactly k consecutive values followed by a different value, the method incorrectly returns True for that different value.

Example:

  • DataStream(value=5, k=2)
  • consec(5) → False (count=1)
  • consec(5) → True (count=2)
  • consec(3) → True (WRONG! Should be False)

The Fix: Always reset the counter BEFORE checking the condition:

def consec(self, num: int) -> bool:
    if num != self.target_value:
        self.consecutive_count = 0
    else:
        self.consecutive_count += 1
    return self.consecutive_count >= self.min_consecutive

Pitfall 2: Not Handling Edge Cases Properly

The Mistake: Forgetting that the counter can grow indefinitely and not considering potential integer overflow in languages with fixed integer sizes, or trying to "optimize" by capping the counter:

def consec(self, num: int) -> bool:
    if num == self.target_value:
        self.consecutive_count = min(self.consecutive_count + 1, self.min_consecutive)  # WRONG optimization
    else:
        self.consecutive_count = 0
    return self.consecutive_count >= self.min_consecutive

While this might seem like an optimization, it doesn't actually provide any benefit in Python and could cause confusion. In some cases, knowing the exact count might be useful for debugging or future extensions.

The Fix: Keep the implementation simple and let the counter grow naturally:

def consec(self, num: int) -> bool:
    self.consecutive_count = 0 if num != self.target_value else self.consecutive_count + 1
    return self.consecutive_count >= self.min_consecutive

Pitfall 3: Misunderstanding the "Last k Values" Requirement

The Mistake: Some might incorrectly think they need to store the last k values and check them all:

def __init__(self, value: int, k: int):
    self.target_value = value
    self.min_consecutive = k
    self.last_k_values = []  # Unnecessary storage!

def consec(self, num: int) -> bool:
    self.last_k_values.append(num)
    if len(self.last_k_values) > self.min_consecutive:
        self.last_k_values.pop(0)
  
    if len(self.last_k_values) < self.min_consecutive:
        return False
  
    return all(v == self.target_value for v in self.last_k_values)  # O(k) time!

This approach uses O(k) space and O(k) time per operation, which is inefficient.

The Fix: Recognize that you only need to track consecutive occurrences with a single counter, achieving O(1) space and time complexity.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

What's the output of running the following function using input [30, 20, 10, 100, 33, 12]?

1def fun(arr: List[int]) -> List[int]:
2    import heapq
3    heapq.heapify(arr)
4    res = []
5    for i in range(3):
6        res.append(heapq.heappop(arr))
7    return res
8
1public static int[] fun(int[] arr) {
2    int[] res = new int[3];
3    PriorityQueue<Integer> heap = new PriorityQueue<>();
4    for (int i = 0; i < arr.length; i++) {
5        heap.add(arr[i]);
6    }
7    for (int i = 0; i < 3; i++) {
8        res[i] = heap.poll();
9    }
10    return res;
11}
12
1class HeapItem {
2    constructor(item, priority = item) {
3        this.item = item;
4        this.priority = priority;
5    }
6}
7
8class MinHeap {
9    constructor() {
10        this.heap = [];
11    }
12
13    push(node) {
14        // insert the new node at the end of the heap array
15        this.heap.push(node);
16        // find the correct position for the new node
17        this.bubble_up();
18    }
19
20    bubble_up() {
21        let index = this.heap.length - 1;
22
23        while (index > 0) {
24            const element = this.heap[index];
25            const parentIndex = Math.floor((index - 1) / 2);
26            const parent = this.heap[parentIndex];
27
28            if (parent.priority <= element.priority) break;
29            // if the parent is bigger than the child then swap the parent and child
30            this.heap[index] = parent;
31            this.heap[parentIndex] = element;
32            index = parentIndex;
33        }
34    }
35
36    pop() {
37        const min = this.heap[0];
38        this.heap[0] = this.heap[this.size() - 1];
39        this.heap.pop();
40        this.bubble_down();
41        return min;
42    }
43
44    bubble_down() {
45        let index = 0;
46        let min = index;
47        const n = this.heap.length;
48
49        while (index < n) {
50            const left = 2 * index + 1;
51            const right = left + 1;
52
53            if (left < n && this.heap[left].priority < this.heap[min].priority) {
54                min = left;
55            }
56            if (right < n && this.heap[right].priority < this.heap[min].priority) {
57                min = right;
58            }
59            if (min === index) break;
60            [this.heap[min], this.heap[index]] = [this.heap[index], this.heap[min]];
61            index = min;
62        }
63    }
64
65    peek() {
66        return this.heap[0];
67    }
68
69    size() {
70        return this.heap.length;
71    }
72}
73
74function fun(arr) {
75    const heap = new MinHeap();
76    for (const x of arr) {
77        heap.push(new HeapItem(x));
78    }
79    const res = [];
80    for (let i = 0; i < 3; i++) {
81        res.push(heap.pop().item);
82    }
83    return res;
84}
85

Recommended Readings

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

Load More