Problem overview
The function find receives a sorted IntArray called nums and an integer value. It returns the index where value sits in nums, or -1 when the value isn't there. The array is sorted in ascending order, and that is the fact the whole solution relies on.
In the example, nums = [1, 2, 4, 5] and value = 4. The value 4 is at index 2, so the answer is 2. Searching the same array for 3 returns -1.
The statement asks for O(log n) time and O(1) extra space.
Approach: halve the search range each time
Linear search checks the elements one by one from the start. It works on any array, but it takes O(n) time, because the value may be at the very end or missing. When the array is sorted you can do much better.
Look at the element in the middle of the range. There are three cases:
- It equals
value: you found it. - It is smaller than
value: because the array is sorted, everything to its left is smaller still, so the value can only be to the right. - It is larger than
value: everything to its right is larger too, so the value can only be to the left.
In the last two cases, one comparison throws away half of the remaining elements. Halving repeatedly takes about log₂(n) steps: a million elements need roughly 20 comparisons.
The range is tracked with two indices, left and right, both inclusive. Searching [1, 2, 4, 5] for 4: the range is indices 0 to 3, the middle index is 1, and nums[1] is 2, which is smaller than 4, so the range becomes 2 to 3. The middle is now index 2, and nums[2] is 4, so the function returns 2.
Step by step
- Set the bounds.
leftstarts at 0 andrightatnums.size - 1, the last valid index. Both arevar, because they move inside the loop. Both are inclusive:rightpoints at a real element, not one past it. - Loop while the range has elements. The condition is
left <= right. With<=, a range of exactly one element still gets checked, and the loop ends only whenleftpassesright. - Find the middle.
middleisleft + (right - left) / 2. Integer division rounds down, so the result is always a valid index in the range. - Found it. If
nums[middle] == value, returnmiddleimmediately. - Too small. If
nums[middle] < value, setleft = middle + 1. The middle element is already ruled out, so the next range starts after it. - Too big. Otherwise set
right = middle - 1, for the same reason. - Not found. When the loop ends, every candidate has been discarded, so return
-1.
The solution in Kotlin
class Solution {
fun find(nums: IntArray, value: Int): Int {
var left = 0
var right = nums.size - 1
while (left <= right) {
val middle = left + (right - left) / 2
if (nums[middle] == value) {
return middle
} else if (nums[middle] < value) {
left = middle + 1
} else {
right = middle - 1
}
}
return -1
}
}
Kotlin details that matter:
/on twoIntvalues is integer division, which truncates, so no rounding call is needed. Here the quantity is never negative, so truncating and rounding down agree.left + (right - left) / 2gives the same index as(left + right) / 2, but it can't overflow.Intis 32 bits and wraps silently: withleft = 2_000_000_000andright = 2_100_000_000, the sumleft + rightwraps to a negative number and(left + right) / 2becomes-97483648. The safer form gives2050000000. Arrays that large are rare, but the habit costs nothing.nums.size - 1is-1for an empty array. Thenleft <= rightis false from the start, the loop doesn't run, and the function returns-1.returninside the loop ends the function at once. The finalreturn -1sits after the loop, and it is reached only when no element matched.- The
if/else if/elsechain is a statement here. Kotlin'swhenwould work as well, but the chain mirrors the three cases directly.
Complexity
Time is O(log n): each pass of the loop halves the range, so there are at most about log₂(n) + 1 passes. Space is O(1): the function keeps three integers and doesn't build any collection.
Edge cases and common mistakes
- Using
left < right. The last remaining element is never examined, so a search for the only element of a one-element array fails. - Setting
left = middleorright = middle. The middle element was already checked. Keeping it in the range can leave the range unchanged and make the loop run forever. - Searching an unsorted array. Binary search discards halves based on the order. If the array isn't sorted, the answer is meaningless.
- Value smaller than everything, or larger than everything. The range shrinks to nothing on one side, and the function returns
-1. - Duplicates. If
valueappears several times, the function returns the index of one of them, not necessarily the first.
Testing
[1, 2, 4, 5]withvalue = 4returns2.[1, 2, 4, 5]withvalue = 3returns-1.[]withvalue = 3returns-1.