A peak is any element strictly greater than its neighbours. Even though the array is not sorted, you can find one in O(log n) by following the uphill slope: compare nums[mid] with nums[mid+1] and walk toward the rise.
Return the index of any peak — an element strictly greater than both neighbours — in a possibly unsorted array nums. Treat the out-of-bounds neighbours as -∞, so a peak always exists. For example, nums=[1,2,1,3,5,6,4] → 5 (value 6); index 1 (value 2) is also a valid answer. The requirement is O(log n) time, which rules out a plain scan.
nums[mid] versus its right neighbour nums[mid+1]. If nums[mid] < nums[mid+1] you are on an uphill slope, and because the far-right edge falls off to -∞, the rising values must eventually turn down — a peak is guaranteed to the right. Otherwise you are flat/downhill, so mid itself or something to its left is a peak. Either way you safely discard half the array.lo = 0, hi = nums.length - 1, loop while lo < hi (strict — we converge the window to a single cell rather than test for a hit).mid = lo + Math.floor((hi - lo) / 2). Because lo < hi, mid + 1 is always a valid index — no out-of-bounds read.nums[mid] < nums[mid+1], a peak lies to the right, so lo = mid + 1 (discard mid, it can't be the peak — its right neighbour is bigger).mid could be the peak, so hi = mid — do not use mid - 1, or you might discard the only peak.lo === hi the window is one cell — that index is a peak. Return lo.lo = mid + 1 but hi = mid (not mid - 1). In the rising case mid is provably not a peak so you may skip it; in the falling case mid is still a candidate so you must keep it. Mixing these up either loops forever or skips the answer.Binary search normally needs a sorted array, but it really only needs a monotone decision: at every mid the slope tells you a half that is guaranteed to contain a peak. With the -∞ sentinels at both ends, that guarantee always holds, so halving is always safe.
lo=0, hi=6. Amber cells are the active window. Out-of-bounds neighbours count as -∞, so a peak always exists.1function findPeakElement(nums: number[]): number {2▶ let lo = 0;3▶ let hi = nums.length - 1;45 while (lo < hi) {6 const mid = lo + Math.floor((hi - lo) / 2);7 if (nums[mid] < nums[mid + 1]) {8 lo = mid + 1; // uphill to the right -> a peak lies right9 } else {10 hi = mid; // downhill (or equal) -> a peak is mid or left11 }12 }13 return lo; // lo === hi: the peak14}
function findPeakElement(nums: number[]): number {
let lo = 0;
let hi = nums.length - 1;
while (lo < hi) {
const mid = lo + Math.floor((hi - lo) / 2);
if (nums[mid] < nums[mid + 1]) {
lo = mid + 1; // uphill to the right -> a peak lies right
} else {
hi = mid; // downhill (or equal) -> a peak is mid or left
}
}
return lo; // lo === hi: the peak
}lo and himark the window that is guaranteed to contain a peak. Initially that's the entire array, because the -∞ sentinels at both ends force at least one peak inside.lo < hi: we shrink the window until a single cell remains rather than testing for an exact match. When lo === hi we are done.lo < hi, mid is strictly less than hi, so mid + 1 is always a valid index — the comparison on the next line can never read out of bounds.nums[mid] < nums[mid + 1] the values climb to the right; a peak must be in [mid + 1, hi]. We can drop mid because its right neighbour is larger, so set lo = mid + 1.mid is greater than its right neighbour, so mid itself is a candidate peak. Set hi = mid — crucially not mid - 1, which could throw away the only peak.lo === hi, and by the invariant that cell is a peak. Return lo (equivalently hi).-∞ sentinels provide exactly that.hi = mid and not mid - 1?" In the falling case mid may itself be the peak; excluding it can discard the only answer and break correctness.function findPeakElement(nums: number[]): number {
let lo = 0;
let hi = nums.length - 1;
while (lo < hi) {
const mid = lo + Math.floor((hi - lo) / 2);
if (nums[mid] < nums[mid + 1]) {
lo = mid + 1; // uphill to the right -> a peak lies right
} else {
hi = mid; // downhill (or equal) -> a peak is mid or left
}
}
return lo; // lo === hi: the peak
}Scan left to right and return the first index whose next element is smaller (or that is the last index) — the simplest correct idea, ignoring the O(log n) requirement.
function findPeakElement(nums: number[]): number {
for (let i = 0; i < nums.length - 1; i++) {
if (nums[i] > nums[i + 1]) return i;
}
return nums.length - 1;
}| "find a peak / local maximum" | binary search comparing mid vs mid+1 |
| O(log n) required on unsorted data | binary search on a slope/predicate |
| mountain array peak | same uphill-slope template |
| "return any valid answer" | converge window to one cell, return lo |
function findPeakElement(nums: number[]): number {
let lo = 0, hi = nums.length - 1;
while (lo < hi) {
const mid = lo + Math.floor((hi - lo) / 2);
if (nums[mid] < nums[mid + 1]) lo = mid + 1;
else hi = mid;
}
return lo;
}nums[-1] = nums[n] = -∞ so a peak always exists.nums = [1,2,1,3,5,6,4], which index can the algorithm return?nums[mid] < nums[mid + 1], what do you do?hi = mid. Why not hi = mid - 1?lo < hi (strict) rather than lo <= hi?nums[mid + 1] never goes out of bounds. Why?