Problem overview
The method lowerBound receives an array of integers nums, sorted in ascending order, and an integer value. It returns the smallest index at which value could be inserted so that the array stays sorted.
For nums = [1, 2, 3, 5, 7] and value = 4, inserting 4 at index 3 gives [1, 2, 3, 4, 5, 7], so the answer is 3. For nums = [1, 2, 3] and value = 2, inserting 2 at index 1 gives [1, 2, 2, 3], so the answer is 1: when the value is already present, the answer is the index of its first occurrence.
The lesson asks for O(n) time and O(1) extra space.
Approach: find the first element that is not smaller
The new value belongs right before the first element that is greater than or equal to it. Everything before that element is smaller, and the new value can slide in without breaking the order. Taking the first such element, rather than any later one, gives the smallest valid index.
Because the array is sorted, scanning from the left finds that element: every element before it is smaller than value, and from there on, all are at least as large. If no element is greater than or equal to value, then value is bigger than everything and belongs at the end, at index nums.count.
A sorted array also allows binary search in O(log n). This lesson uses the plain scan, which is simpler and meets the stated bound.
Step by step
- Loop over the indices.
for i in 0..<nums.count. The answer is a position, so the loop needs the index and not just the element. - Return at the first element that is not smaller. If
nums[i] >= value, returni. Using>=rather than>makes an existing equal value count, so duplicates give the index of the first copy. - Return the count after the loop. If the loop finishes, every element is smaller than
value, so it goes after all of them, atnums.count.
The solution in Swift
class Solution {
func lowerBound(_ nums: [Int], _ value: Int) -> Int {
for i in 0..<nums.count {
if nums[i] >= value {
return i
}
}
return nums.count
}
}
Swift details worth knowing:
- Insertion index versus last index. The valid insertion points run from 0 to
nums.countinclusive, one more than the valid element indices. Returningnums.countis therefore correct, even thoughnums[nums.count]would crash if you read it. - Half-open range.
0..<nums.countis empty for an empty array, so the body never runs and the method returns0. - Early return. A
returninside the loop ends the whole method at the first match, so later elements are never examined. - Compare with
find. A plain search compares with==and returns-1when nothing matches. Here the comparison is>=and the fallback is a real position.
Complexity
Time: O(n). The scan stops at the first element that is not smaller than value, but when value is larger than every element, all n are checked.
Space: O(1). Only the loop index is used.
Edge cases and common mistakes
- Value smaller than everything.
[2, 3]withvalue = 1returns0. - Value larger than everything.
[1, 2, 3]withvalue = 9returns3, the length. - Empty array. The result is
0. - Repeated values.
[2, 2, 2]withvalue = 2returns0, the first copy. A strict>would skip all three and return3. - Returning
nums.count - 1for the fallback. That places the value before the last element, not after it. - Unsorted input. The scan returns the first element that is not smaller, which is only the right answer because the input is sorted.
Testing
nums = [1, 2, 3, 5, 7],value = 4gives3.nums = [1, 2, 3],value = 2gives1.nums = [1, 2, 3],value = 9gives3.nums = [2, 2, 2],value = 2gives0.nums = [],value = 5gives0.
