2936. Number of Equal Numbers Blocks
Problem Description
You are given a very large array nums with a special property: all occurrences of the same value appear consecutively. This means if a value appears multiple times in the array, all those occurrences must be adjacent to each other with no other values in between.
Since the array is extremely large, you cannot access it directly. Instead, you're provided with a BigArray class that has two methods:
at(index): Returns the value at the given indexsize(): Returns the total length of the array
Your task is to count how many distinct blocks of equal values exist in the array. A block is a maximal contiguous segment where all elements have the same value.
For example:
- If the array is
[1, 1, 1, 2, 2, 3, 3, 3, 3], there are 3 blocks:[1,1,1],[2,2], and[3,3,3,3] - If the array is
[5, 5, 4, 4, 4, 7], there are 3 blocks:[5,5],[4,4,4], and[7]
The solution uses binary search to efficiently find the boundaries of each block. Starting from index 0, for each new block encountered, it uses binary search to find where that block ends (the first index where the value changes), then moves to the next block. This approach is efficient for very large arrays since it doesn't need to check every single element when dealing with large blocks of the same value.
The time complexity is O(b * log(n)) where b is the number of blocks and n is the array size, which is much better than O(n) when there are long sequences of repeated values.
Intuition
The key insight comes from recognizing that we're dealing with a potentially massive array where consecutive equal values form blocks. If we naively check every single element, we'd need O(n) operations, which could be inefficient for very large arrays.
Think about it this way: if we encounter a block of one million consecutive 5s, do we really need to check all million elements? No! We just need to find where this block ends.
This leads us to the binary search approach. When we're at the start of a block with value x, we know that:
- Elements equal to
xwill continue for some distance - At some point, we'll hit an element that's not equal to
x(marking the end of the block)
We can use binary search to find this boundary efficiently. The search space is from our current position to the end of the array. We're looking for the first index where the value changes from x.
The binary search works because of the problem's guarantee: all occurrences of a value are adjacent. This means once we find a value different from x, we know we've passed the entire block of xs, and there won't be any more xs later in the array.
For each block, instead of checking potentially millions of elements one by one (O(block_size)), we can find the block's end in just O(log(n)) operations using binary search. This is especially powerful when blocks are large.
The bisect_left function with a custom key lambda j: nums.at(j) != x cleverly searches for the first position where the condition "value is not equal to x" becomes True, which is exactly where our current block ends.
Learn more about Binary Search patterns.
Solution Approach
The solution uses the binary search template to efficiently find the boundaries of each block of equal values.
Binary Search Template Applied:
For each block starting at index startIndex, we find the first index where the value differs from targetValue. The feasible function is:
feasible(mid) = nums.at(mid) != targetValue- Pattern: false, false, ..., false, true, true, true
- We find the first True index (first different element)
left, right = start_index, array_size - 1 first_true_index = array_size # Default if all remaining elements are same while left <= right: mid = left + (right - left) // 2 if nums.at(mid) != target_value: first_true_index = mid right = mid - 1 else: left = mid + 1 return first_true_index # Index of first different element
Main Algorithm Flow:
- Start at index 0
- For each block:
- Use binary search to find where the block ends (first different value)
- Increment block counter
- Jump to the end of the current block
- Repeat until we've processed the entire array
Why This Works:
The algorithm leverages the problem's guarantee that all occurrences of a value are consecutive. The binary search finds the exact boundary where values change, allowing us to skip entire blocks efficiently.
Time Complexity:
- For each block, binary search takes
O(log n)time - Total complexity:
O(b × log n)wherebis the number of blocks - This is much better than
O(n)when blocks are large
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 the algorithm with the array [2, 2, 2, 7, 7, 3, 3, 3, 3, 3] which has length 10.
Initial State:
i = 0,n = 10,ans = 0
First Block (value = 2):
- Increment counter:
ans = 1 - Current value:
x = nums.at(0) = 2 - Check next element:
nums.at(1) = 2(same as x) - Since the block continues, use binary search to find where it ends
- Binary search in range [0, 10) looking for first index where value ≠ 2:
- Mid = 5:
nums.at(5) = 3(≠ 2) → search left half - Mid = 2:
nums.at(2) = 2(= 2) → search right half - Mid = 3:
nums.at(3) = 7(≠ 2) → search left half - Found: index 3 is the first position where value ≠ 2
- Mid = 5:
- Jump to index 3:
i = 0 + 3 = 3
Second Block (value = 7):
- Increment counter:
ans = 2 - Current value:
x = nums.at(3) = 7 - Check next element:
nums.at(4) = 7(same as x) - Use binary search in range [3, 10) looking for first index where value ≠ 7:
- Mid = 6:
nums.at(6) = 3(≠ 7) → search left half - Mid = 4:
nums.at(4) = 7(= 7) → search right half - Mid = 5:
nums.at(5) = 3(≠ 7) → found
- Mid = 6:
- Jump to index 5:
i = 3 + (5-3) = 5
Third Block (value = 3):
- Increment counter:
ans = 3 - Current value:
x = nums.at(5) = 3 - Check next element:
nums.at(6) = 3(same as x) - Use binary search in range [5, 10) looking for first index where value ≠ 3:
- Mid = 7:
nums.at(7) = 3(= 3) → search right half - Mid = 9:
nums.at(9) = 3(= 3) → search right half - Search reaches end of array at index 10
- Mid = 7:
- Jump to index 10:
i = 5 + (10-5) = 10
Termination:
i = 10which equalsn, so we exit the loop- Return
ans = 3
The algorithm correctly identified 3 blocks: [2,2,2], [7,7], and [3,3,3,3,3], making only 11 array accesses instead of checking all 10 elements sequentially.
Solution Implementation
1from bisect import bisect_left
2from typing import Optional
3
4# Definition for BigArray.
5# class BigArray:
6# def at(self, index: int) -> int:
7# pass
8# def size(self) -> int:
9# pass
10
11class Solution:
12 def countBlocks(self, nums: Optional["BigArray"]) -> int:
13 """
14 Count the number of contiguous blocks of equal elements in a BigArray.
15
16 A block is defined as a maximal contiguous subarray where all elements are equal.
17
18 Args:
19 nums: A BigArray object with at() and size() methods
20
21 Returns:
22 The number of distinct blocks in the array
23 """
24 # Initialize index pointer and get total size
25 current_index = 0
26 total_size = nums.size()
27 block_count = 0
28
29 # Iterate through the array
30 while current_index < total_size:
31 # Each iteration represents a new block
32 block_count += 1
33
34 # Get the value at current position
35 current_value = nums.at(current_index)
36
37 # Check if next element is different (small optimization for single elements)
38 if current_index + 1 < total_size and nums.at(current_index + 1) != current_value:
39 # If next element is different, this block has only one element
40 current_index += 1
41 else:
42 # Use binary search to find the end of the current block
43 # bisect_left finds the first position where the condition becomes True
44 # The condition checks if element at position j is different from current_value
45 block_length = bisect_left(
46 range(current_index, total_size),
47 True,
48 key=lambda j: nums.at(j) != current_value
49 )
50
51 # Move index to the start of the next block
52 current_index += block_length
53
54 return block_count
551/**
2 * Definition for BigArray.
3 * class BigArray {
4 * public BigArray(int[] elements);
5 * public int at(long index);
6 * public long size();
7 * }
8 */
9class Solution {
10 /**
11 * Counts the number of contiguous blocks in the array where each block
12 * contains identical consecutive elements.
13 *
14 * @param nums The BigArray to analyze
15 * @return The total number of distinct blocks
16 */
17 public int countBlocks(BigArray nums) {
18 int blockCount = 0;
19 long currentIndex = 0;
20 long arraySize = nums.size();
21
22 // Iterate through the array, jumping to the end of each block
23 while (currentIndex < arraySize) {
24 // Find the end of the current block of identical elements
25 currentIndex = findBlockEnd(nums, currentIndex, arraySize);
26 blockCount++;
27 }
28
29 return blockCount;
30 }
31
32 /**
33 * Uses binary search template to find the first index where value differs.
34 * Feasible function: nums.at(mid) != targetValue
35 * Pattern: false, false, ..., false, true, true, true
36 * We find the first true index (first different element).
37 */
38 private long findBlockEnd(BigArray nums, long startIndex, long arraySize) {
39 int targetValue = nums.at(startIndex);
40 long left = startIndex;
41 long right = arraySize - 1;
42 long firstTrueIndex = arraySize; // Default: no different element found
43
44 while (left <= right) {
45 long mid = left + (right - left) / 2;
46 if (nums.at(mid) != targetValue) {
47 // Found a different element
48 firstTrueIndex = mid;
49 right = mid - 1;
50 } else {
51 // Same value, search right
52 left = mid + 1;
53 }
54 }
55
56 return firstTrueIndex;
57 }
58}
591/**
2 * Definition for BigArray.
3 * class BigArray {
4 * public:
5 * BigArray(vector<int> elements);
6 * int at(long long index);
7 * long long size();
8 * };
9 */
10class Solution {
11public:
12 int countBlocks(BigArray* nums) {
13 using ll = long long;
14 ll arraySize = nums->size();
15 int blockCount = 0;
16
17 // Lambda using binary search template to find first index where value differs
18 // Feasible function: nums->at(mid) != targetValue
19 // Pattern: false, false, ..., false, true, true, true
20 auto findBlockEnd = [&](ll startIdx) -> ll {
21 int targetValue = nums->at(startIdx);
22 ll left = startIdx;
23 ll right = arraySize - 1;
24 ll firstTrueIndex = arraySize; // Default: no different element found
25
26 while (left <= right) {
27 ll mid = left + (right - left) / 2;
28 if (nums->at(mid) != targetValue) {
29 firstTrueIndex = mid;
30 right = mid - 1;
31 } else {
32 left = mid + 1;
33 }
34 }
35
36 return firstTrueIndex;
37 };
38
39 // Iterate through the array, jumping from one block to the next
40 for (ll currentIdx = 0; currentIdx < arraySize; ++blockCount) {
41 currentIdx = findBlockEnd(currentIdx);
42 }
43
44 return blockCount;
45 }
46};
471/**
2 * Counts the number of contiguous blocks of identical elements in a BigArray.
3 * A block is defined as a contiguous sequence of identical elements.
4 */
5function countBlocks(nums: BigArray | null): number {
6 if (!nums) {
7 return 0;
8 }
9
10 const arraySize: number = nums.size();
11
12 /**
13 * Binary search template to find first index where value differs.
14 * Feasible function: nums.at(mid) !== targetValue
15 * Pattern: false, false, ..., false, true, true, true
16 */
17 const findBlockEnd = (startIndex: number): number => {
18 const targetValue: number = nums.at(startIndex);
19 let left: number = startIndex;
20 let right: number = arraySize - 1;
21 let firstTrueIndex: number = arraySize; // Default: no different element found
22
23 while (left <= right) {
24 const mid: number = Math.floor((left + right) / 2);
25 if (nums.at(mid) !== targetValue) {
26 firstTrueIndex = mid;
27 right = mid - 1;
28 } else {
29 left = mid + 1;
30 }
31 }
32
33 return firstTrueIndex;
34 };
35
36 let blockCount: number = 0;
37 let currentIndex: number = 0;
38
39 while (currentIndex < arraySize) {
40 currentIndex = findBlockEnd(currentIndex);
41 blockCount++;
42 }
43
44 return blockCount;
45}
46Time and Space Complexity
Time Complexity: O(m × log n), where m is the number of distinct blocks (consecutive elements with the same value) in the array, and n is the length of the array.
The algorithm iterates through the array and for each distinct block:
- When encountering a new value that differs from the next element (the
ifbranch), it moves forward by 1 position inO(1)time - When encountering a value that continues in a block (the
elsebranch), it uses binary search viabisect_leftto find the end of the current block, which takesO(log n)time
Since we process each of the m blocks exactly once, and in the worst case each block requires a binary search operation, the total time complexity is O(m × log n).
Space Complexity: O(1)
The algorithm only uses a constant amount of extra space for variables i, n, ans, and x, regardless of the input size. The bisect_left operation with the range object and lambda function doesn't create additional space proportional to the input size, as the range object is a lazy iterator and the lambda function is evaluated on-the-fly.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Using Wrong Binary Search Template Variant
Pitfall: Using while left < right with right = mid instead of the standard template.
Common incorrect pattern:
# WRONG: Non-standard template while left < right: mid = (left + right) >> 1 if nums.at(mid) != target: right = mid else: left = mid + 1 return left
Solution: Use the standard template with first_true_index:
# CORRECT: Standard template left, right = start_index, array_size - 1 first_true_index = array_size while left <= right: mid = left + (right - left) // 2 if nums.at(mid) != target: first_true_index = mid right = mid - 1 else: left = mid + 1 return first_true_index
2. Incorrect Binary Search Range
Pitfall: When using Python's bisect_left with range(), the returned index is relative to the range, not absolute.
Solution: For clearer code, use explicit binary search with first_true_index tracking.
3. Edge Case: Last Block Extends to End
Pitfall: Not handling the case where no different value is found.
Solution: Initialize first_true_index = array_size so that when no different element exists, we correctly return the array size as the block end.
4. Optimization Check Boundary Issue
Pitfall: Accessing nums.at(current_index + 1) without bounds checking.
Solution:
if current_index + 1 < total_size and nums.at(current_index + 1) != current_value: current_index += 1
Which algorithm should you use to find a node that is close to the root of the tree?
Recommended Readings
https assets algo monster cover_photos Binary_Search svg Binary Search Intuition Binary search is an efficient array search algorithm It works by narrowing down the search range by half each time If you have looked up a word in a physical dictionary you've already used binary search in real life Let's
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
Recursion If you prefer videos here's a video that explains recursion in a fun and easy way Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask him
Want a Structured Path to Master System Design Too? Don’t Miss This!