Find Peak Element
A peak is an element strictly greater than its neighbours. Given an unsorted array where nums[i] != nums[i+1], return the index of any peak in O(log n). Treat out-of-bounds neighbours as negative infinity.
- 1 <= nums.length <= 1000
- -2³¹ <= nums[i] <= 2³¹ - 1
- nums[i] != nums[i + 1] for all valid i.
Intuition
Find peak element returns the index of any element strictly greater than both neighbours, in O(log n). That an unsorted array admits a logarithmic search is the surprising part, and the reason is worth understanding.
Binary search normally needs sorted data. Here the enabling property is different — the problem states that nums[i] != nums[i+1] for all i, and that out-of-bounds neighbours count as negative infinity:
- Comparing nums[mid] with nums[mid + 1] reveals a slope, and a slope always leads to a peak in that direction.
If nums[mid] < nums[mid + 1], the sequence is ascending at mid. Following it rightward, it either keeps rising until the array ends — where the boundary acts as negative infinity, making the last element a peak — or it turns downward at some point, which is a peak. Either way a peak exists to the right, so the left half can be discarded.
If nums[mid] > nums[mid + 1], the same argument applies leftward, with mid itself still a candidate.
That guarantee is what makes the halving valid without sorted input.
The loop runs while left < right, and the descending case sets right = mid rather than mid - 1, since mid may be the peak. There is no equality case to handle, because adjacent elements are never equal.
Any peak is acceptable, which is why finding a peak rather than the maximum is enough — and it is why the O(n) scan for the global maximum, while correct, does more work than asked.
Binary search without a sorted array. Comparing nums[mid] to its right neighbour tells you which side must contain a peak: if the slope rises, a peak exists to the right; if it falls, one exists at or left of mid. The out-of-bounds -∞ convention guarantees a peak always exists.
Approach
Before reading on: price up what the direct approach costs here, then ask what property lets you throw away half the range after one comparison. Aim for O(log n) time and O(1) space.
See why binary search applies
The array is unsorted, but adjacent elements are never equal and boundaries act as negative infinity. Those two facts, not sorting, are what enable the halving.
Compare mid with its right neighbour
Test nums[mid] against nums[mid + 1]. This single comparison reveals a slope, which is all the direction information the search needs.
Follow an ascending slope right
If nums[mid] < nums[mid + 1], the sequence rises. It either rises to the array's end — a peak by the boundary rule — or turns downward, also a peak. Either way a peak lies right, so set left = mid + 1.
Follow a descending slope left
If nums[mid] > nums[mid + 1], a peak lies at or left of mid. Set right = mid, not mid - 1, since mid itself may be the peak.
Skip the equality case
Adjacent elements are guaranteed distinct, so no third branch is needed. This is why the search never stalls.
Return either converged index
The loop ends when left and right meet, and that index is a peak. Any peak is acceptable — the problem does not ask for the global maximum.
Cost of the search
The range halves each step, giving O(log n) time and O(1) space — better than the O(n) scan that finding the global maximum would require.
Solution & live demo
Common pitfalls
Setting hi to mid - 1 on a descent
else:
hi = mid - 1else:
hi = midWhen nums[mid] > nums[mid + 1], mid itself may be the peak. Excluding it can discard the only answer in that half and the search converges on a non-peak.
Comparing with both neighbours
if nums[mid-1] < nums[mid] > nums[mid+1]: return mid
if nums[mid] < nums[mid + 1]:
Needs bounds guards on both sides and doesn't help the search decide where to go when the test fails. The single right-neighbour comparison always determines a half that must contain a peak.
Scanning linearly
for i in range(n):
if is_peak(i): return iwhile lo < hi:
Correct but O(n), and the problem asks for O(log n). The slope argument makes half the array discardable at every step even though nothing is sorted.
Edge cases
The loop never runs and index 0 is returned — correct, since both neighbours are negative infinity.
Every comparison takes the ascending branch, driving lo to the last index, which is the peak.
Every comparison takes the descending branch, driving hi to 0, which is the peak.
Any one is acceptable. The search commits to whichever half it enters and returns the peak it finds there.