What searching means
Searching is the task of finding where a value sits in a collection, or learning that it isn't there. The simplest method is a linear search: look at the elements one by one from the start until you find the value or run out of elements. On an array of n elements that takes up to n comparisons, so its time is O(n).
If the array is sorted, you can do far better. Binary search looks at the middle element and compares it with the target. If the middle element is too small, the target can only be in the right half. If it is too big, the target can only be in the left half. Either way, half of the remaining elements are ruled out with one comparison. Repeating this shrinks the range from n to n / 2, n / 4, and so on until it is empty, so the time is O(log n). An array of a million elements needs about twenty comparisons. The extra space is O(1): just a few integers.
The problem
The method find receives a sorted Array[Int] called nums and an Int called value. It returns the index of value in nums, or -1 if the value is not there. For Array(1, 2, 4, 5) and value = 4 the answer is 2, because nums(2) is 4.
The idea in detail
Keep two markers, left and right, that bound the part of the array where the value could still be. Both are inclusive: they are indices of elements that haven't been ruled out. At the start, left is 0 and right is nums.length - 1.
Each round, compute the middle index and compare:
- If
nums(middle)equalsvalue, returnmiddle. - If it is smaller, everything at or before
middleis too small, so movelefttomiddle + 1. - Otherwise it is bigger, so everything at or after
middleis too big andrightmoves tomiddle - 1.
When left passes right, no candidates remain, and the answer is -1.
Searching for 4 in Array(1, 2, 4, 5): left is 0 and right is 3, so middle is 1 and nums(1) is 2. That is smaller than 4, so left becomes 2. Now the middle is 2 + (3 - 2) / 2 = 2, and nums(2) is 4, a match. The method returns 2.
Step by step
- Declare the markers
leftandrightwithvar, because both change in the loop. They start at 0 andnums.length - 1. - Loop while the range is non-empty:
while (left <= right). Use<=, not<: when the two are equal, one element is still unchecked. - Compute the middle as a
val:left + (right - left) / 2. Dividing twoIntvalues truncates, so there is no rounding to do. - Found it. If
nums(middle) == value, returnmiddle. Arrays are indexed with parentheses in Scala. - Too small. If
nums(middle) < value, setleft = middle + 1. The element atmiddlehas been ruled out, so skip it. - Too big. Otherwise set
right = middle - 1, for the same reason. - Not found. After the loop, return -1.
The solution in Scala
class Solution {
def find(nums: Array[Int], value: Int): Int = {
var left = 0
var right = nums.length - 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
}
}
Scala details and gotchas
- Why not
(left + right) / 2.Intin Scala is 32 bits and overflow wraps silently. Withleft = 1500000000andright = 2000000000, the sum 3500000000 doesn't fit and the expression gives -397483648, a negative index.left + (right - left) / 2gives 1750000000. The differenceright - leftcan't overflow here, because it is never larger thanright. For real arrays, which are far smaller than two billion elements, both forms work, but the safe one is a good habit. - Integer division truncates.
7 / 2is 3. For non-negative numbers that means the middle of an even-sized range is the left one of the two central elements. - Return inside
while. Thereturn middlesits in awhileloop in the method body, so it simply leaves the method. Scala allows the finalreturn -1, or you could write just-1as the last expression. - Empty array. With
nums.length == 0,rightstarts at -1, the loop condition0 <= -1is false, and the method returns -1 without indexing the array. - Several equal values. If the value appears more than once, the method returns the index of one of them, not necessarily the first.
Common mistakes
- Using
left < right. A range of exactly one element is never checked, soArray(1, 2, 4, 5)with value 5 could be reported as missing. - Setting
left = middleorright = middle. The element atmiddlehas already been compared. Keeping it lets the range stop shrinking, and the loop can run forever. For example, whenleftequalsmiddle,left = middlechanges nothing. - Running it on an unsorted array. The discard logic depends on the order. On unsorted data the method can discard the half that holds the value.
- Putting the -1 inside the loop. The
-1belongs after the loop. Reaching it means every candidate was discarded.
Testing
Array(1, 2, 4, 5), value 4 gives2.Array(1, 2, 4, 5), value 1 gives0, and value 5 gives3.Array(1, 2, 4, 5), value 3 gives-1, and value 9 gives-1.Array(), value 3 gives-1.
Key takeaways
- Binary search needs a sorted array and runs in
O(log n)time withO(1)extra space. - Keep inclusive bounds, loop with
left <= right, and move the bounds tomiddle + 1ormiddle - 1. - Compute the middle as
left + (right - left) / 2to avoidIntoverflow.
