Problem overview
The function count receives a sorted vector nums and a target, and returns how many times target appears. For nums = [1, 2, 2, 2, 4, 5] and target = 2 the answer is 3.
The statement asks for O(log n) time and O(1) extra space, so scanning the whole vector and counting (O(n)) is too slow.
Approach: the range of equal values is bounded by two lower bounds
Because the vector is sorted, all copies of target sit next to each other. If you know where that block starts and where it ends, the count is the difference of the two positions.
Both positions come from the same tool, the lower bound: the index of the first element that is not less than a given value. Then:
- the lower bound of
targetis the index where the block oftargetstarts; - the lower bound of
target + 1is the index of the first element greater thantarget, since the elements are integers. That is one past the end of the block.
The number of occurrences is the second index minus the first. If target is missing, both searches land on the same index and the difference is 0, with no special case.
On [1, 2, 2, 2, 4, 5] with target = 2: the lower bound of 2 is index 1, and the lower bound of 3 is index 4 (the element 4 is the first one not below 3). The answer is 4 - 1 = 3.
For a missing value, take target = 3 in the same vector. The lower bound of 3 is index 4, because 4 is the first element not below 3. The lower bound of 4 is also index 4. The difference is 0, which is correct: nothing sits between the two positions.
Step by step
The lesson builds count first, assuming the helper exists, and then writes the helper.
- Find the start.
loisSelf::lower_bound_idx(&nums, target). The&numslends the vector as a slice, sonumsisn't moved into the call. - Find the end.
hiis the same call withtarget + 1. - Return the difference
(hi - lo) as i32. The indexes areusizeand the function returnsi32, and the subtraction is in parentheses becauseasbinds tighter than-. - Declare the helper
lower_bound_idx(nums: &[i32], value: i32) -> usize. - Use a half-open range.
leftis0usizeandrightisnums.len(), one past the last index, so the range is[left, right). This avoids the -1 that an inclusive upper bound would need for an empty vector, which ausizecan't hold. - Loop while
left < right. A half-open range is empty once the bounds meet. - Compute
midasleft + (right - left) / 2. It always lies inside the range. - If
nums[mid] < value, the answer is to the right:left = mid + 1. - Otherwise
midcould itself be the answer, so it must stay in the range:right = mid. With a half-open range,mid - 1would be wrong, because it would excludemid. - Return
left, the first index whose element is at leastvalue, ornums.len()if there is none.
The solution in Rust
impl Solution {
fn lower_bound_idx(nums: &[i32], value: i32) -> usize {
let mut left = 0usize;
let mut right = nums.len();
while left < right {
let mid = left + (right - left) / 2;
if nums[mid] < value {
left = mid + 1;
} else {
right = mid;
}
}
left
}
fn count(nums: Vec<i32>, target: i32) -> i32 {
let lo = Self::lower_bound_idx(&nums, target);
let hi = Self::lower_bound_idx(&nums, target + 1);
(hi - lo) as i32
}
}
Rust details worth a look:
- Slices. The helper takes
&[i32], a borrowed view of the elements. Passing&nums, a&Vec<i32>, works because Rust converts it to a slice automatically, and the vector stays usable for the second call. - Associated functions. Neither function takes
self, so the helper is called asSelf::lower_bound_idx. - Half-open ranges and
usize. Sincerightis never decreased belowleft, andright - leftis only computed insideleft < right, the unsigned subtraction can't go below zero. - Casting the result.
asconvertsusizetoi32and truncates silently if the value doesn't fit; vector lengths in this lesson are far below that limit. - Tail expressions. Both functions end without a semicolon, so
leftand(hi - lo) as i32are their return values.
Complexity
Time: O(log n). count runs two binary searches, each halving its range every iteration, so it does about 2 log₂ n iterations in total, which is still O(log n).
Space: O(1). The helper keeps three integers and borrows the vector instead of copying it.
Edge cases and common mistakes
- Value missing.
[1, 2, 2, 2, 4, 5]with3returns 0, and with0or9it also returns 0, because both bounds land on the same index. - Empty vector. Both searches return 0, so the difference is 0.
- All elements equal.
[3, 3, 3]with3returns 3: the start is 0 and the end is the length. - The largest
i32.target + 1overflows whentargetisi32::MAX. In an optimized build it wraps toi32::MIN,hibecomes 0, andhi - lowraps as ausize, socount(vec![1, i32::MAX], i32::MAX)returns-1instead of 1. A debug build would panic. Inputs in the lesson stay clear of this, but a general-purpose version needs a second search that compares with<=. - Subtracting in the wrong order.
lo - hiwould underflow theusize. - Using
right = mid - 1in the half-open helper. It can drop the answer from the range, and withusizeit can underflow whenmidis 0. - Counting with a linear scan. It gives correct results but misses the
O(log n)goal.
Testing
[1, 2, 2, 2, 4, 5]with2gives3.[1, 2, 2, 2, 4, 5]with3gives0.[3, 3, 3]with3gives3.[]with9gives0.
