Problem overview
The function lower_bound receives a sorted vector of integers, nums, and an integer value. It returns the smallest index at which value could be inserted so that the vector stays sorted. The value doesn't have to be in the vector, and the vector isn't changed.
For nums = [1, 2, 3, 5, 7] and value = 4, the answer is 3: inserting 4 at index 3 gives [1, 2, 3, 4, 5, 7]. For nums = [1, 2, 3] and value = 2, the answer is 1: inserting 2 there gives [1, 2, 2, 3]. The statement asks for O(n) time and O(1) extra space.
Approach: find the first element that is not smaller
Everything before the insertion point must be smaller than value, and everything from it onward must be at least as large. So the answer is the index of the first element that is greater than or equal to value. Because the vector is sorted, once one element passes that test, all the following ones do as well, which is why the first match is the right one.
This lesson finds it with a left-to-right scan, the same shape as a linear search. The difference is the test: a linear search stops on ==, while the lower bound stops on >=. If the scan finds no such element, every element is smaller than value, and it belongs at the end, at index nums.len().
Sortedness also allows a faster method, binary search, which halves the search range each step. The scan is the simpler version and fits the O(n) budget given here.
Step by step
- Loop over the indices with
for i in 0..nums.len(). The answer is a position, so the loop works with indices rather than elements. - Test with
>=. Ifnums[i] >= value, returni as i32. Using>=rather than>makes a value that already exists map to the index of its first occurrence. - Return the length after the loop. If no element reached the value, return
nums.len() as i32, the position just past the last element.
The solution in Rust
impl Solution {
fn lower_bound(nums: Vec<i32>, value: i32) -> i32 {
for i in 0..nums.len() {
if nums[i] >= value {
return i as i32;
}
}
return nums.len() as i32;
}
}
The Rust details worth a closer look:
- Two casts, same reason. Both
iandnums.len()areusize, while the function returns ani32. Rust doesn't convert integer types on its own, so each return needsas i32. Without it the compiler reports a type mismatch. - The range is half-open.
0..nums.len()excludes the length, sonums[i]never reads past the end. The length only appears in the final return, as a position and not as an index to read. - Early return from a loop.
returnleaves the whole function from inside thefor, so the line after the loop runs only when no element qualified. - The standard library has this. For a sorted vector,
nums.partition_point(|&x| x < value)returns the same position as ausize, using binary search. Writing the scan yourself shows what it computes.
Complexity
Time: O(n). The loop runs at most n times, once per element, doing a constant amount of work each time. It stops sooner when an early element is large enough, but the worst case, a value larger than everything, scans the whole vector.
Space: O(1). One loop variable, and no copy of the vector.
Edge cases and common mistakes
- Value larger than everything.
[1, 2, 3]with value 9 returns3, the length. - Value smaller than everything.
[1, 2, 3]with value 0 returns0, because the first element already passes the test. - Value already present.
[1, 2, 3]with value 2 returns1. With>instead of>=the loop would skip past the 2 and return2. - Duplicates.
[1, 2, 2, 2, 3]with value 2 returns1, the first of the 2s. That is the smallest index where inserting 2 keeps the order. - Empty vector. The loop doesn't run and the function returns
0, the only possible position. - Returning the element. The answer is
i, notnums[i]. - Returning
-1at the end. That is the linear search convention. Here a missing value still has a valid insertion point. - An unsorted vector. The reasoning depends on the order. On an unsorted vector, the first element that is not smaller doesn't mark an insertion point for a sorted result.
Testing
nums = [1, 2, 3, 5, 7],value = 4gives3.nums = [1, 2, 3],value = 2gives1.nums = [1, 2, 3],value = 9gives3.nums = [1, 2, 3],value = 0gives0.nums = [],value = 5gives0.
