LeetCode 2936 - Number of Equal Numbers Blocks
The problem is asking us to count the number of maximal contiguous blocks of equal numbers in a very large array nums. A block is maximal if it contains all consecutive occurrences of the same number, and numbers are guaranteed to appear in consecutive segments, i.e.
Difficulty: 🟡 Medium
Topics: Array, Binary Search, Interactive
Solution
Problem Understanding
The problem is asking us to count the number of maximal contiguous blocks of equal numbers in a very large array nums. A block is maximal if it contains all consecutive occurrences of the same number, and numbers are guaranteed to appear in consecutive segments, i.e., the array is already "grouped" by value. The key challenge is that nums is extremely large - up to $10^{15}$ elements - which makes iterating through the array directly infeasible. Instead, we are provided a BigArray interface with two operations: at(index) to access an element and size() to get the length.
The input represents a 0-indexed array where all equal numbers are adjacent. The expected output is a single integer indicating how many contiguous blocks of equal numbers exist in the array.
Key constraints to note are the array's size (up to $10^{15}$) and the guarantee that all equal numbers are adjacent. This means we can leverage binary search to efficiently find block boundaries instead of iterating element by element.
Important edge cases include arrays where all numbers are the same, arrays with all distinct numbers, and very small arrays (length 1 or 2). The guarantee of adjacency simplifies the problem because we never have to handle "scattered duplicates."
Approaches
Brute Force
The naive approach is to iterate through the array from start to end, counting the number of times a value changes. Each time the value at the current index differs from the previous, we increment the block count. This works correctly for small arrays but requires $O(n)$ calls to at(index), which is impossible for $n \leq 10^{15}$.
Optimal Approach
The key observation is that all occurrences of a value are contiguous, so each block can be found using binary search. Instead of checking every element, we can jump ahead to the last occurrence of the current value in logarithmic steps. For each block starting at index i, we perform a binary search to find the first index where the value changes. We then move i to that new index and repeat until we reach the end of the array. This reduces the number of at() calls from linear to roughly $O(b \log n)$, where b is the number of blocks.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n) | O(1) | Iterates over all elements; impractical for very large arrays |
| Optimal | O(b * log n) | O(1) | Uses binary search to jump to block boundaries efficiently |
Algorithm Walkthrough
-
Initialize
index = 0andcount = 0.indexwill track the start of the current block. -
Retrieve the length of the array via
nums.size(). -
While
index < n, do the following: -
Increment
countsince we have found a new block starting atindex. -
Store the value of the current block in
current_val = nums.at(index). -
Use binary search to find the first index
rightwherenums.at(right) != current_val. Startleft = index + 1andright = n. Whileleft < right, check the middle element. If it is equal tocurrent_val, movelefttomid + 1, otherwise moverighttomid. -
Set
index = rightto move to the start of the next block. -
Return
countwhenindex >= n.
Why it works: The algorithm relies on the invariant that all occurrences of a number are contiguous. Binary search guarantees that we efficiently find the boundary of each block without inspecting every element. Incrementing count each time a new block is encountered ensures we count all blocks exactly once.
Python Solution
# Definition for BigArray.
# class BigArray:
# def at(self, index: long) -> int:
# pass
# def size(self) -> long:
# pass
class Solution(object):
def countBlocks(self, nums: 'BigArray') -> int:
n = nums.size()
count = 0
index = 0
while index < n:
count += 1
current_val = nums.at(index)
left, right = index + 1, n
while left < right:
mid = left + (right - left) // 2
if nums.at(mid) == current_val:
left = mid + 1
else:
right = mid
index = right
return count
Implementation Walkthrough: We start at index 0 and initialize count to 0. Each iteration of the outer loop represents finding a new block. We retrieve the current value and use binary search to locate the end of the block. After the binary search, index points to the start of the next block. This continues until we reach the end of the array.
Go Solution
/**
* Definition for BigArray.
* type BigArray interface {
* At(int64) int
* Size() int64
* }
*/
func countBlocks(nums BigArray) int {
n := nums.Size()
count := 0
var index int64 = 0
for index < n {
count++
currentVal := nums.At(index)
left, right := index+1, n
for left < right {
mid := left + (right-left)/2
if nums.At(mid) == currentVal {
left = mid + 1
} else {
right = mid
}
}
index = right
}
return count
}
Go Notes: Go uses int64 for indices because the array can be extremely large. The binary search and main loop logic is identical to the Python version. The handling of integers and array access is straightforward due to the provided BigArray interface.
Worked Examples
Example 1: nums = [3,3,3,3,3]
| Step | index | current_val | left | right | count |
|---|---|---|---|---|---|
| 1 | 0 | 3 | 1 | 5 | 1 |
| 2 | 5 | - | - | - | 1 |
Result: 1 block
Example 2: nums = [1,1,1,3,9,9,9,2,10,10]
| Step | index | current_val | left | right | count |
|---|---|---|---|---|---|
| 1 | 0 | 1 | 1 | 10 | 1 |
| 2 | 3 | 3 | 4 | 10 | 2 |
| 3 | 4 | 9 | 5 | 10 | 3 |
| 4 | 7 | 2 | 8 | 10 | 4 |
| 5 | 8 | 10 | 9 | 10 | 5 |
| 6 | 10 | - | - | - | 5 |
Result: 5 blocks
Example 3: nums = [1,2,3,4,5,6,7]
Each element is a single block, count increments from 1 to 7. Result: 7 blocks.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(b * log n) | Each block requires a binary search to find its end; total time proportional to number of blocks times log of array size |
| Space | O(1) | Only counters and temporary variables are used; no extra memory scales with input size |
Since n can be extremely large, iterating directly is infeasible. This algorithm efficiently jumps to block boundaries using binary search.
Test Cases
# test cases
# single block
assert Solution().countBlocks(BigArrayMock([3,3,3,3,3])) == 1 # all equal
# multiple blocks
assert Solution().countBlocks(BigArrayMock([1,1,1,3,9,9,9,2,10,10])) == 5
# all distinct
assert Solution().countBlocks(BigArrayMock([1,2,3,4,5,6,7])) == 7
# single element
assert Solution().countBlocks(BigArrayMock([42])) == 1
# two identical elements
assert Solution().countBlocks(BigArrayMock([5,5])) == 1
# two distinct elements
assert Solution().countBlocks(BigArrayMock([5,6])) == 2
# large uniform block (simulate smaller)
assert Solution().countBlocks(BigArrayMock([7]*100)) == 1
| Test | Why |
|---|---|
| single block | Validates the simplest case of all identical values |
| multiple blocks | Ensures multiple transitions are counted correctly |
| all distinct | Each element is a block |
| single element | Edge case of smallest array |
| two identical | Minimal size with repeated elements |
| two distinct | Minimal size with distinct elements |
| large uniform | Checks efficiency of binary search in a |