DSA sheet · Binary Search · Classic pattern (last problem)

Find Peak Element

Find any index whose value is bigger than both its neighbours, in O(log n), on an array that is not sorted. The teacher shows that binary search still works, because comparing nums[mid] with one neighbour tells us which side is guaranteed to have a peak. She then solves it two ways: leaning left and leaning right. The second way gets a TLE (time limit exceeded) on screen, and she fixes it by picking the second middle. That fix is the real lesson of this video.

Why it matters: it proves binary search needs a reliable yes/no direction, not a sorted array. It also teaches the most common binary search bug: an endless loop when you write left = mid.

Every part below follows the same order:
① the question in simple words → ② what the constraints tell us → ③ intuition → ④ building the conditions from examples → ⑤ approach steps → ⑥ code → ⑦ code line by line → ⑧ dry run → ⑨ complexity & remember

Part 0 · Before starting

Why binary search works (and why it doesn't need a sorted array)

Binary search checks the middle and then throws away half of the range. All it needs is a test at mid that tells us, for sure, "an answer exists on this side". Sorted arrays give us such a test easily, but they're not the only way. In this problem the test is "is nums[mid] bigger than its right neighbour?" As we'll see, either answer points to a side where a peak is guaranteed.

left, right, mid

index:  0  1  2  3          left = 0, right = 3: two middles
value:  1  2  3  1
           ↑  ↑
   first mid  second mid
   0 + 3//2 = 1   0 + (3+1)//2 = 2

What is a peak?

An element is a peak if it is strictly greater than its left neighbour and its right neighbour. For the first and last elements, the missing neighbour counts as −∞ (minus infinity), so an edge element only has to beat its one real neighbour. (LeetCode states this as nums[-1] = nums[n] = -∞.)


Part A · Brute force: check every index

LeetCode 162

1The question in simple words

Given nums, return the index of any peak. If there are several peaks, any one of them is accepted.

index0123
value12313 > 2 and 3 > 1 → answer 2
index0123456
value1213564two peaks: 2 (index 1) and 6 (index 5) → 1 or 5

In the second array, 2 beats 1 and 1. And 6 beats 5 and 4. The 5 is not a peak (5 > 3 but 5 < 6).

2What the constraints tell us

3Intuition

Walk through the array and, at each index, ask: "am I bigger than the left neighbour and the right neighbour?" The first index that says yes is a peak.

4Building the check

The teacher starts the loop at index 1 and checks nums[i-1] and nums[i+1]. On [1,2,3,1]: index 1 (2) beats 1 but not 3 ✗. Index 2 (3) beats 2 and 1 ✓ → return 2.

Doubt (a fix to the spoken version): what about the first and last index?
→ A loop that only checks indices 1 to n − 2 misses peaks at the edges. [3,2,1] has its only peak at index 0. [1,2,3] has it at index 2. And [7] has just index 0. All three would return nothing. The fix: treat a missing neighbour as −∞, so every index (edges too) gets checked. The code below does that.

5Approach steps

  1. For each index i: left_val = nums[i-1], or −∞ if i = 0. right_val = nums[i+1], or −∞ if i = n − 1.
  2. If nums[i] is bigger than both → return i.

6Code (Python)

Brute force: check every index
class Solution:
    def findPeakElement(self, nums):
        n = len(nums)
        for i in range(n):
            left_val = nums[i - 1] if i > 0 else float('-inf')
            right_val = nums[i + 1] if i < n - 1 else float('-inf')
            if nums[i] > left_val and nums[i] > right_val:
                return i
        return -1   # never reached: a peak always exists

7Code line by line

linewhat it means
left_val = nums[i - 1] if i > 0 else float('-inf')The left neighbour. Index 0 has none, so use −∞ (anything beats it). Careful: in Python nums[-1] would quietly give the last element, so we must not just write nums[i-1].
right_val = ...Same for the right side and the last index.
if nums[i] > left_val and nums[i] > right_valStrictly bigger than both → peak.
return -1Only for safety. With the −∞ edges and no equal neighbours, a peak always exists. The largest value, for example, is always one.

8Dry run

[1,2,1,3,5,6,4]: i = 0: 1 > −∞ but 1 < 2 ✗ · i = 1: 2 > 1 and 2 > 1 ✓ → return 1. (A linear scan always finds the leftmost peak.)

9Complexity & remember

RememberMissing neighbours count as −∞. Don't forget the two edge indices.

Part B · Binary search leaning left (compare with mid + 1)

1The question (same as Part A)

Return any peak index, in O(log n).

2What the constraints tell us

3Intuition: think of a mountain

The teacher's observation: if the array has only one peak, everything to the left of it goes up (sorted ascending), and everything to its right goes down (sorted descending). Like a mountain:

            6
          5   4             values 1 3 5 6 4 2
        3       2           left of 6: going up   (ascending)
      1                     right of 6: going down (descending)

With several peaks there are several hills, so the array isn't "two sorted halves" any more. But the local idea still holds. Stand at mid and look at the next cell:

The key insightComparing nums[mid] with nums[mid+1] tells us a side that definitely has a peak. There might be peaks on the other side too, but we don't care: any peak is accepted.

4Building the conditions from examples

Example 1: mid is bigger than mid + 1, but mid is NOT a peak

index:   0   1   2   3   4   5   6
value:   2   7   5  [4]  3   9   8
                     M
nums[3] = 4  >  nums[4] = 3     → downhill to the right

4 beats its right neighbour, but it isn't a peak (4 < 5 on its left). Still, we're sure there's a peak at index 3 or to its left: walking left, 4 → 5 → 7, and 7 beats 2 and 5. So we search left..mid.

Notice there's also a peak on the right (9, at index 5). The teacher's point: she isn't saying "there's no peak on the right". She's saying "there's surely one on the left", and that's enough.

Doubt 1: what if nothing on the left is bigger, so the left side has "no peak"?
→ It can't happen, thanks to the −∞ edge. The teacher tests this on [6,5,4,3,9,8]. mid = 2 (4 > 3) → go left. mid = 1 (5 > 4) → go left. mid = 0 (6 > 5) → go left. We end on index 0. 6 has no left neighbour, so it only needs to beat 5. It's a peak, and LeetCode accepts it. (9 at index 4 is a peak too; returning either passes.)

Rule: right = mid when going left, left = mid + 1 otherwise

Teacher's general ruleWhenever mid can be the answer, move the pointer exactly to mid. Whenever mid can't be the answer, move past it (mid ± 1).

The loop: while left < right

We stop when left and right meet. A peak is always kept inside left..right, so the single cell left must be a peak. Return left (or right, same index; the teacher shows both pass).

Doubt 2: is nums[mid + 1] always a valid index?
→ Yes. Inside the loop left < right, and mid rounds down, so mid < right ≤ n − 1. That means mid + 1 ≤ n − 1.

5Approach steps

  1. left = 0, right = n - 1.
  2. While left < right: mid = left + (right - left) // 2 (first middle).
  3. If nums[mid] > nums[mid + 1] → right = mid.
  4. Else → left = mid + 1.
  5. Return left.

6Code (Python)

Binary search leaning left
class Solution:
    def findPeakElement(self, nums):
        left, right = 0, len(nums) - 1

        while left < right:
            mid = left + (right - left) // 2      # first middle

            if nums[mid] > nums[mid + 1]:         # downhill: peak at mid or left
                right = mid                       # mid may be the peak, keep it
            else:                                 # uphill: peak to the right
                left = mid + 1                    # mid is surely not a peak

        return left                               # left == right

7Code line by line

linewhat it means
left, right = 0, len(nums) - 1Search the whole array.
while left < right:More than one candidate left. Stop at one.
mid = left + (right - left) // 2The first (lower) middle. Guarantees mid + 1 is in range.
if nums[mid] > nums[mid + 1]:Next cell is lower → a peak exists at mid or to the left.
right = midDrop the right side but keep mid.
left = mid + 1Next cell is higher → mid isn't a peak; a peak exists to the right.
return leftThe one remaining index is a peak.

8Dry run

Run 1: [1,2,1,3,5,6,4]

stepleftrightmidnums[mid]nums[mid+1]decisionthrown away
1063353 < 5 uphill → left = 4indices 0–3
2465646 > 4 downhill → right = 5index 6
3454565 < 6 uphill → left = 5index 4
end55return 5 (value 6, a peak ✓)
index0123456
step 112135643 < 5 → go right
step 212135646 > 4 → keep 6, go left
step 312135645 < 6 → go right
end1213564index 5 ✓

The peak at index 1 was thrown away in step 1. That's fine: the problem accepts any peak.

Run 2: [1,2,3,1]

l 0, r 3, mid 1: 2 < 3 → left = 2 · l 2, r 3, mid 2: 3 > 1 → right = 2 · return 2 ✓

Run 3: [6,5,4,3,9,8] (peak at the left edge)

mid 2: 4 > 3 → right = 2 · mid 1: 5 > 4 → right = 1 · mid 0: 6 > 5 → right = 0 · return 0 ✓

9Complexity & remember

RememberLower mid. nums[mid] > nums[mid+1] → right = mid, else left = mid + 1. while left < right, return left.

Part C · Binary search leaning right (compare with mid − 1), the TLE and its fix

Since any peak is fine, the teacher flips the idea: compare mid with its left neighbour and lean right. Steps 1–2 are the same as Part B. What changes is the condition, the pointer moves, and (after a TLE) the choice of mid.

3Intuition

Looking right first, you check the left neighbour. Looking left first (Part B), you check the right neighbour.

4The first attempt, and why it gets TLE

Leaning right with the FIRST middle (gets stuck, don't use)
class Solution:
    def findPeakElement(self, nums):
        left, right = 0, len(nums) - 1
        while left < right:
            mid = left + (right - left) // 2      # first middle: the bug
            if nums[mid] > nums[mid - 1]:
                left = mid
            else:
                right = mid - 1
        return left

On [1,2,3,1]:

stepleftrightmidnums[mid] vs nums[mid−1]decision
10312 > 1left = 1
21323 > 2left = 2
32323 > 2left = 2 (no change!)
4…2323 > 2left = 2 … forever → TLE

When only two cells are left (left = 2, right = 3), the first middle is left. Setting left = mid doesn't move anything, so the loop never ends.

On some arrays the loop happens to end through the right = mid - 1 branch before this two-cell trap, which is why the bug can hide until a test case like this one hits it.

The fix: take the SECOND middle

The teacher's fix: when the range has two middles, pick the second (upper) one. Add 1 before halving:

just the changed line
mid = left + (right - left + 1) // 2      # second middle

Now with left = 2, right = 3: mid = 2 + (1 + 1) // 2 = 3. Either left = 3 (the range closes) or right = 2 (the range closes). Progress every time.

Doubt 1: how do I know which middle to use in general?
→ Look at the branch that keeps mid.
• right = mid (Part B): use the first middle. mid < right, so right always shrinks.
• left = mid (Part C): use the second middle. mid > left, so left always grows.
If they don't match, a 2-cell range can freeze and you get an infinite loop.
Doubt 2: is nums[mid - 1] always valid now?
→ Yes. With the second middle, mid > left ≥ 0, so mid − 1 ≥ 0. (With the first middle, mid could be 0, and Python's nums[-1] would silently read the last element. That's one more reason the first-middle version is wrong.)

5Approach steps

  1. left = 0, right = n - 1.
  2. While left < right: mid = left + (right - left + 1) // 2 (second middle).
  3. If nums[mid] > nums[mid - 1] → left = mid.
  4. Else → right = mid - 1.
  5. Return left.

6Code (Python)

Binary search leaning right (second middle)
class Solution:
    def findPeakElement(self, nums):
        left, right = 0, len(nums) - 1

        while left < right:
            mid = left + (right - left + 1) // 2  # SECOND middle

            if nums[mid] > nums[mid - 1]:         # came uphill into mid
                left = mid                        # peak at mid or right, keep mid
            else:                                 # mid lost to its left neighbour
                right = mid - 1                   # peak surely on the left

        return left

7Code line by line (what differs from Part B)

linewhat it means
mid = left + (right - left + 1) // 2Rounds up, so mid is never equal to left. That's what lets left = mid make progress.
if nums[mid] > nums[mid - 1]:Compare with the left neighbour because we want to lean right.
left = midMid may be the peak, so keep it.
right = mid - 1Mid is not a peak (smaller than its left neighbour), so drop it.

8Dry run

Run 1: [1,2,3,1], the array that froze before

stepleftrightmid (second)nums[mid] vs nums[mid−1]decision
10323 > 2left = 2
22331 < 3right = 2
end22return 2 ✓

Run 2: [1,2,1,3,5,6,4]

index0123456
step 11213564mid = 0 + 7//2 = 3; 3 > 1 → left = 3
step 21213564mid = 3 + 4//2 = 5; 6 > 5 → left = 5
step 31213564mid = 5 + 2//2 = 6; 4 < 6 → right = 5
end1213564return 5 ✓

9Complexity, the teacher's advice & remember

The teacher's advice: for every binary search problem, write it both ways (lean left and lean right). When one way breaks, work out why. Here, doing that is how you discover the "second middle" rule. An educator can't show every variation in one video, so trying the other direction yourself and debugging it is how you really learn.

Rememberleft = mid needs the second middle: left + (right - left + 1) // 2. Otherwise two cells can freeze → infinite loop / TLE.

Part D · Revision page

LinearLean left (Part B)Lean right (Part C)
compareboth neighboursnums[mid] vs nums[mid+1]nums[mid] vs nums[mid-1]
mid formula-first: left + (right-left)//2second: left + (right-left+1)//2
if truereturn iright = midleft = mid
elsenext ileft = mid + 1right = mid - 1
timeO(n)O(log n)O(log n)
If you remember only 5 lines 1. Peak = strictly bigger than both neighbours; outside the array counts as −∞.
2. Going down to the right → a peak is at mid or left. Going up → a peak is to the right.
3. Any peak is accepted, so a side that surely has one is enough to throw the other half away.
4. If mid may be the answer, move to mid; if not, move past it.
5. right = mid → first middle. left = mid → second middle.
Mistakes to avoid ✗ thinking binary search needs a sorted array (it needs a reliable direction)
✗ left = mid with the first middle (infinite loop, TLE)
✗ skipping index 0 and n − 1 in the linear scan
✗ using Python's nums[-1] as "the left neighbour of index 0" (it's the last element!)
✗ while left <= right with a branch that keeps mid
test it yourself (paste under any solution above)
s = Solution()
print(s.findPeakElement([1, 2, 3, 1]))            # 2
print(s.findPeakElement([1, 2, 1, 3, 5, 6, 4]))   # 1 or 5 (both are peaks)
print(s.findPeakElement([6, 5, 4, 3, 9, 8]))      # 0 or 4
print(s.findPeakElement([7]))                     # 0

Based on this video: Find Peak Element