2526. Find Consecutive Integers from a Data Stream
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:
-
Constructor
DataStream(int value, int k)
: Initializes the data structure with:value
: the target integer that we want to check fork
: the number of consecutive integers we need to examine- An initially empty stream of integers
-
Method
consec(int num)
:- Adds a new integer
num
to the stream - Returns
true
if the lastk
integers in the stream are ALL equal tovalue
- Returns
false
if:- Any of the last
k
integers is different fromvalue
, OR - There are fewer than
k
integers in the stream so far
- Any of the last
- Adds a new integer
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.
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 lastk
integers cannot all be equal tovalue
(at least not until we've seenk
more integers that are all equal tovalue
) - 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:
-
Instance Variables:
self.val
: Stores the target value we're checking forself.k
: Stores the required number of consecutive occurrencesself.cnt
: Counter to track the current streak of consecutive values equal toself.val
-
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
-
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
: Resetself.cnt
to 0 (streak is broken) - If
num == self.val
: Incrementself.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
ifself.cnt >= self.k
(we have at leastk
consecutive values) - Return
False
otherwise
- If
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 EvaluatorExample 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
orself.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
, andself.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.
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
Queue Intro Think of the last time you stood in line to buy movie tickets The first person in line gets their ticket first and any newcomers join the end of the line This system where the first person to arrive is the first to be served is a queue in real
Median of Data Stream Given a stream of numbers find the median number at any given time accurate to 1 decimal place Example add_number 1 add_number 2 add_number 3 get_median 2 0 add_number 4 get_median 2 5 Try it yourself Explanation Intuition Brute force way is to sort the entire list every time we get a new number This would be O N log N for each add_number operation One step up is to notice that the list is sorted before we add any new number to it So every
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!