LeetCode #704 Easy

Binary Search

Binary Search is LeetCode 704 (Easy). You are given an integer array nums sorted in ascending order, with no repeated values, and an integer target. Return the index of target in nums, or -1 if it is not there.

The solution must run in O(log n) time, so a linear scan that checks every element is not accepted.

Constraints
  • 1 <= nums.length <= 10⁴
  • -10⁴ < nums[i], target < 10⁴
  • All integers in nums are unique
  • nums is sorted in ascending order
arraybinary search
Open on LeetCode ↗
02

Intuition

Because the array is sorted, one comparison removes half of it. If the middle element is smaller than the target, everything to its left is smaller too and can be discarded; if it is larger, everything to its right can go. Either way, the part that could still hold the target halves.

That halving is the whole algorithm, and it sets the running time: n elements are settled in about log₂ n comparisons, so a million elements need only 20. The idea is simple; the difficulty is bookkeeping, keeping the window boundaries consistent so no element is skipped and the loop always ends.

How to spot this pattern

A sorted array (or any ordered search space) plus a test that tells you which side the answer is on. That test does not have to be nums[mid] < target; any yes/no condition that flips once from false to true can be searched the same way, which is how capacity and speed problems become binary search on the answer.

03

Approach

Try it first

Before reading on, decide: is hi the last index or one past it? What must the loop condition be for that choice? What happens with a single-element array?

1

Keep a closed window [lo, hi]

Start with lo = 0 and hi = len(nums) - 1, both inclusive. The invariant is: if the target exists, its index is in [lo, hi]. Every later update is chosen to keep that sentence true, which is how you know each move is safe.

2

Loop while the window is not empty

Use while lo <= hi. When lo == hi there is still one unchecked element, so < would skip it. When lo > hi the window is empty and the target is absent.

3

Compute the middle without overflow

mid = lo + (hi - lo) // 2. In C++ and Java, (lo + hi) / 2 can exceed the 32-bit range and become negative; the subtraction form never overflows.

4

Compare and discard a half

  • nums[mid] == target: return mid.
  • nums[mid] < target: lo = mid + 1.
  • nums[mid] > target: hi = mid - 1.

Always move past mid; it has already been checked, and keeping it can make the loop run forever.

5

Return -1 after the loop

If the loop ends without returning, the window is empty and every index has been ruled out, so return -1. The invariant guarantees this is right: the target would have stayed inside the window, and nothing is left there. This iterative version needs only O(1) extra space.

04

Binary Search solution in Python | C++ | Java

▶1class Solution:
▶2 def search(self, nums: List[int], target: int) -> int:
▶3 lo, hi = 0, len(nums) - 1
▶4 while lo <= hi:
▶5 mid = lo + (hi - lo) // 2
▶6 if nums[mid] == target:
▶7 return mid
▶8 if nums[mid] < target:
▶9 lo = mid + 1
▶10 else:
▶11 hi = mid - 1
▶12 return -1
-1001325394125lohiwindow 6/6target 9
lo0first index still possible
hi5last index still possible
target9
The window [lo, hi] holds every index where 9 could still be. At the start that is the whole array. The loop keeps one promise: if the target exists, it is inside the window. Each comparison will cut the window in half.
-1001325394125lomidhiwindow 6/63 < 9 → go right
mid2middle of [0, 5]
nums[mid]33 < 9
Look at the middle: nums[2] = 3. It is smaller than 9. Because the array is sorted, everything at or left of index 2 is smaller too, so none of it can be the target. Writing lo + (hi - lo) // 2 instead of (lo + hi) // 2 avoids integer overflow in C++ and Java.
-1001325394125lohiwindow 3/6lo = mid + 1 = 3
lo3moved past mid
hi5unchanged
left to search3indices
Discard the half that cannot contain 9. The new window is [3, 5]. Moving to mid ± 1, not mid, matters: mid is already ruled out, and keeping it could loop forever.
-1001325394125lomidhiwindow 3/6nums[4] = 9 → found
mid4middle of [3, 5]
nums[mid]9equals the target
comparisons2for 6 elements
Found at index 4 after 2 comparisons. A linear scan would have looked at 5 elements; binary search needs at most about log₂(6) + 1 = 3.
05

Recursive binary search

Each call searches the window [lo, hi], returns -1 once it is empty, and otherwise recurses into the one half that can still hold the target.

▶1class Solution:
▶2 def search(self, nums: List[int], target: int) -> int:
▶3 def find(lo: int, hi: int) -> int:
▶4 if lo > hi:
▶5 return -1
▶6 mid = lo + (hi - lo) // 2
▶7 if nums[mid] == target:
▶8 return mid
▶9 if nums[mid] < target:
▶10 return find(mid + 1, hi)
▶11 return find(lo, mid - 1)
▶12 
▶13 return find(0, len(nums) - 1)
06

Common pitfalls

Using lo < hi with an inclusive hi

✗ Wrong
lo, hi = 0, len(nums) - 1
while lo < hi:
✓ Right
lo, hi = 0, len(nums) - 1
while lo <= hi:

When lo == hi one element is still unchecked. The strict condition exits first, so [5] with target 5 wrongly returns -1.

Not moving past mid

✗ Wrong
if nums[mid] < target:
    lo = mid
else:
    hi = mid
✓ Right
if nums[mid] < target:
    lo = mid + 1
else:
    hi = mid - 1

mid is already ruled out. With lo = mid, a window of two elements can stop shrinking and the loop never ends.

Overflow in the midpoint

✗ Wrong
int mid = (lo + hi) / 2;
✓ Right
int mid = lo + (hi - lo) / 2;

For large arrays lo + hi can exceed INT_MAX and wrap negative, giving an invalid index. This exact bug sat in Java's own Arrays.binarySearch for years.

07

Edge cases

Target at either end

Index 0 and index n - 1 are reachable only if hi starts at n - 1 and the loop runs while lo <= hi. Test both ends: they catch off-by-one bugs that a target in the middle never exposes.

Target absent

The window empties (lo > hi) and the loop exits. Return -1 after the loop, not from inside it.

08

Complexity

Time
O(log n)
Space
O(1)
Each comparison halves the window, so at most ⌊log₂ n⌋ + 1 comparisons. Iterative binary search needs O(1) space; the recursive version is also O(log n) time but uses O(log n) stack.
09

Binary search variants

The loop stays the same; what changes is what you do on a match and which bound you return.

ProblemQuestionOn nums[mid] == targetReturns
Binary Search (704)is the target present?return midindex or -1
Search Insert Position (35)first index with value >= targethi = mid - 1, keep goinglo
First and Last Position (34)leftmost and rightmost matchrecord mid, keep searching one sidetwo indices
Search in Rotated Array (33)target in a rotated sorted arrayreturn midcheck which half is sorted first
Koko Eating Bananas (875)smallest speed that workssearch over answers, not indiceslo
10

Binary Search FAQ

What is binary search?

Binary search finds a value in a sorted array by repeatedly comparing it with the middle element and discarding the half that cannot contain it. It runs in O(log n) time instead of the O(n) of a linear scan.

What are the steps?
  • Precondition: the array is sorted in ascending order.
  • Initialise: lo = 0, hi = n - 1.
  • Loop: while lo <= hi, set mid = lo + (hi - lo) / 2.
  • Compare: equal, return mid; nums[mid] < target, set lo = mid + 1; else set hi = mid - 1.
  • End: return -1.
  • Complexity: O(log n) time, O(1) space.
  • Example: [-1, 0, 3, 5, 9, 12], target 9: mid 2 (3 < 9), mid 4 (9 found), answer 4.
How fast is it?

O(log n). After k comparisons the window has at most n / 2^k elements, so it is empty after about log₂ n + 1 steps. Best case is O(1), when the first middle element is the target. Space is O(1) iteratively.

Why use lo + (hi - lo) / 2 instead of (lo + hi) / 2?

In languages with fixed-size integers, lo + hi can overflow for very large arrays and produce a negative index. lo + (hi - lo) / 2 gives the same middle without ever exceeding hi. In Python both are safe.