What searching means
Searching is the task of finding where a value sits in a collection, or learning that it is not there. The simplest method is the linear search: look at the elements one by one from the start until you find the value or reach the end. For an array of n elements that takes up to n steps, so it is O(n).
If the array is sorted, you can do far better. Binary search compares the target with the middle element and, based on that one comparison, throws away half of the remaining range. Repeating this halves the range again and again, so it needs about log₂ n steps: roughly 20 comparisons for a million elements, and about 30 for a billion.
The idea
Keep two indices, left and right, that mark the part of the array that could still contain the target. Both are inclusive. At each step look at the middle element:
- If it equals the target, return its index.
- If it is smaller than the target, everything at or before the middle is also too small, because the array is sorted. Continue in the right half.
- If it is larger, everything at or after the middle is too large. Continue in the left half.
When the range becomes empty, the target is not in the array.
For nums = [1, 2, 4, 5] and value = 4: the range is indices 0 to 3, and the middle is index 1, holding 2. That is smaller than 4, so the range becomes indices 2 to 3. The middle is index 2, holding 4, which matches, so the answer is 2.
The exercise, step by step
The method Find(int[] nums, int value) returns the index of value in the sorted array nums, or -1 when it is missing. The statement asks for O(log n) time and O(1) extra space.
- Set up the bounds.
leftstarts at 0 andrightatnums.Length - 1, the last index. Both are inclusive, and arrays report their size through theLengthproperty. - Loop while
left <= right. The range holds at least one element as long aslefthas not passedright. With<instead, the last remaining element would never be checked. - Compute
middle.left + (right - left) / 2is the index halfway between them. Dividing twointvalues in C# truncates, so no rounding call is needed. - Found it. If
nums[middle] == value, returnmiddle. - Too small. If
nums[middle] < value, setleft = middle + 1. The element atmiddleis already ruled out, so skip past it. - Too big. Otherwise set
right = middle - 1, for the same reason. - Not found. After the loop, return
-1. Reaching that line means every candidate was discarded.
The solution in C#
class Solution {
public int Find(int[] nums, int value) {
int left = 0, right = nums.Length - 1;
while (left <= right) {
int 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;
}
}
C# details
- Why
left + (right - left) / 2. The expression(left + right) / 2gives the same index, but the sumleft + rightcan exceedint.MaxValuefor huge arrays. C# does not warn about this: by default anintoverflow wraps around silently to a negative number, and the index would then be invalid. Subtracting first keeps every intermediate value in range. - Declaring two variables at once.
int left = 0, right = nums.Length - 1;declares twointvariables of the same type in one statement. - The
+ 1and- 1matter. If you wroteleft = middlewhennums[middle] < value, then withleft == middlethe range would never shrink and the loop would run forever. - Return type. The method returns an
intindex, and-1is a safe "not found" signal because no valid index is negative.
The built-in version
C# already has binary search in the library: Array.BinarySearch(nums, value) returns the index if the value is found. If it is not found, it returns a negative number, the bitwise complement (~) of the index where the value would be inserted, so you cannot compare against exactly -1. Writing it yourself, as in this lesson, is how you learn the pattern that many harder problems reuse.
Complexity
Time: O(log n). Each iteration halves the range, so the loop runs at most about log₂ n + 1 times.
Space: O(1). The method uses three integers, whatever the array size.
Edge cases and common mistakes
- Empty array.
rightis-1, the loop condition0 <= -1is false, and the method returns-1. - Value at either end.
Find([1, 2, 4, 5], 1)returns 0 andFind([1, 2, 4, 5], 5)returns 3. - Value missing.
Find([1, 2, 4, 5], 3)returns-1. - Using
<in the loop. The last remaining element is skipped, and searches for it fail. - Unsorted input. Binary search relies on the order. On an unsorted array it can miss values that are present.
- Duplicates. With repeated values, the method returns the index of some match, not necessarily the first or last one. Finding a boundary needs a different variant, such as the lower bound.
Key takeaways
- Binary search needs a sorted array and runs in
O(log n), againstO(n)for a linear search. - Keep an inclusive range
[left, right]and loop while it is non-empty. - Move past
middlewithmiddle + 1ormiddle - 1, never tomiddleitself. - Compute the midpoint as
left + (right - left) / 2to avoid overflow.