StudyHub

Algorithms · Searching

Peak finding

Find a "peak" in [2, 5, 7, 8, 6, 3, 1]: a number at least as big as its neighbours. Look at the middle, 8, and its right neighbour, 6: we're going downhill, so a peak is at 8 or to its left. Narrow in, always walking uphill, and you land on 8. That's peak finding: follow the slope.

Why it matters: every array has at least one peak, and you can find one in $O(\log n)$ comparisons, without sorting and without looking at most of the array.

Prerequisites

  • Binary search: peak finding uses the same halving loop.
  • Comparing an element with its neighbour.
  • The idea of a "local" maximum versus the "global" maximum.

The analogy

Everyday analogy

You're hiking in thick fog and can only see one step ahead. Rule: always step uphill. You're guaranteed to reach a summit, though maybe not the highest mountain.

Peak finding does this on an array. At the middle, it checks which way is uphill and throws away the other half, because a summit must exist on the uphill side.

How it works

Data requirement: none. Any array works, sorted or not. Treat positions outside the array as $-\infty$.

Start with one search by hand

Take [2, 5, 7, 8, 6, 3, 1], indices 0 to 6. At each step compare a[mid] with a[mid + 1]. Guess first, then open "Check".

Example: predict before you read

Step 1. mid = 3: compare 8 with 6. Predict: which side keeps a peak?

Check

8 > 6, so it's downhill to the right. A peak is at 3 or to its left: keep 0..3.

Step 2. Cover the next line and guess: mid = 1, compare 5 with 7. Where now?

Check

5 < 7 is uphill to the right, so keep 2..3.

Step 3. mid = 2, compare 7 with 8. Predict where the range ends.

Check

Uphill again, so keep 3..3. One element is left: index 3, value 8, is a peak. 3 comparisons.

Peak finding compares mid with its right neighbour and walks uphill: 8 > 6 go left, 5 < 7 go right, 7 < 8 go right, peak 8 at index 3.
What you just did by hand: each row compares the yellow pair and drops the downhill side (grey).

Quick recall: Before reading further, cover the text above and say out loud: if a[mid] < a[mid + 1], why must a peak exist on the right?

Check your answer

Keep walking right from mid + 1. Either the values eventually stop rising, which is a peak, or they rise to the end, and the last element is a peak.

Finish the picture

Peaks don't have to be the maximum. Try [1, 3, 2, 4, 6, 5], which has two peaks, 3 and 6.

  • Step 1. mid = 2: compare 2 with 4. Predict the direction.
    Check

    2 < 4 is uphill to the right, so keep 3..5.

  • Step 2. mid = 4: compare 6 with 5. Predict the peak returned.
    Check

    Downhill, so keep 3..4; then mid = 3, 4 < 6, keep 4..4. It returns 6, at index 4. The peak 3 at index 1 is never seen, and that's fine.

Quick recall: Without scrolling up, say out loud: why is returning 6 and ignoring 3 still a correct answer?

Check your answer

The task is to find a peak, any element at least as big as its neighbours. Both qualify.

The rule in plain words

Look at the middle and its right neighbour. If the neighbour is bigger, the slope goes up to the right: keep the right part. Otherwise keep the middle and the left part.

When one element is left, it is a peak.

Quick recall: Before you read the code, write the three steps of one round in your own words. Then check below.

Check your answer

1. Compare the middle with the next element. 2. If the next one is bigger, move to the right part. 3. Otherwise keep the middle and everything to its left.

The code

Read this code after you have done one search by hand. It will make more sense.

int find_peak(const int a[], int n) {         /* returns the index of a peak */
    int lo = 0, hi = n - 1;
    while (lo < hi) {                          /* more than one candidate */
        int mid = lo + (hi - lo) / 2;          /* mid < hi, so mid + 1 is valid */
        if (a[mid] < a[mid + 1]) lo = mid + 1; /* uphill: peak on the right */
        else hi = mid;                         /* downhill: peak at mid or left */
    }
    return lo;
}

Definition

What you just did, in one line: a peak is an element with $a_{i-1} \le a_i \ge a_{i+1}$, and peak finding locates one by always keeping the half on the uphill side of the middle.

flowchart TD
  A["lo = 0, hi = n - 1"] --> B{"lo < hi ?"}
  B -->|no| C["Return lo: it is a peak"]
  B -->|yes| D["mid = middle of lo..hi"]
  D --> E{"a[mid] < a[mid+1] ?"}
  E -->|"yes: uphill"| F["lo = mid + 1"]
  E -->|"no: downhill"| G["hi = mid"]
  F --> B
  G --> B
          
Before you scroll past this diagram, cover the right side and guess: on [2, 5, 7, 8, 6, 3, 1], which arrow do you take first? (Answer: "no: downhill", since 8 > 6.)

How fast is it?

Quick recall: Before looking at the formula, predict: how many elements does a simple left-to-right scan check in the worst case?

Check your answer

All $n$, if the array keeps rising and the peak is the last element.

Each round halves the candidate range, and the loop runs until one element is left:

$$\lceil \log_2 n \rceil \text{ comparisons} \;=\; O(\log n), \qquad \text{versus } O(n) \text{ for a linear scan.}$$

For our 7 elements, that's $\lceil \log_2 7 \rceil = 3$, exactly what we used.

BestAverageWorstExtra spaceData requirement
$O(\log n)$$O(\log n)$$O(\log n)$$O(1)$None (any array)

Warning

This finds a peak, not the maximum. Finding the global maximum of an unsorted array still needs $O(n)$, because the biggest value could be anywhere.

Exam tip

Be ready to justify "a peak exists on the uphill side": follow the rising values; they either turn down (a peak) or reach the edge, which is a peak because outside counts as $-\infty$.

Common mistakes

  • Writing hi = mid - 1 on the downhill branch, which can throw away the peak at mid itself.
  • Using lo <= hi: with hi = mid the range may stop shrinking, so the loop never ends.
  • Thinking the answer is the largest element. It's only guaranteed to beat its neighbours.

Try it yourself

Exercise 1 (warm-up)

Find a peak in [1, 2, 3, 4, 5] with the algorithm. Which index is returned?

Hint

Every comparison is uphill.

Answer

Index 4, value 5. The last element is a peak because outside the array counts as $-\infty$.

Exercise 2 (a bit harder)

Run the algorithm on [9, 1, 2, 3, 4, 5, 0]. Which peak does it return, and is it the maximum? Write your answer down before you open it.

Show answer

mid = 3 (3 < 4) → keep 4..6; mid = 5 (5 > 0) → keep 4..5; mid = 4 (4 < 5) → keep 5..5. It returns 5 at index 5. It's a valid peak but not the maximum, which is 9 at index 0.

Did you know?

Did you know?

MIT's famous 6.006 course opened its first lecture with peak finding. It shows in five minutes how a smarter idea turns $O(n)$ into $O(\log n)$.

The same "follow the slope" idea is the heart of hill climbing and gradient methods used in machine learning, which also find local, not always global, best points.

Key points to remember

If you remember one thing from each line, remember the because.

  • A peak always exists because the maximum element is one, and rising values must end somewhere.
  • The uphill half must contain a peak because walking up it either turns down or reaches the edge.
  • It is $O(\log n)$ because each comparison halves the candidate range.
  • It may miss the maximum because it only needs a local peak and ignores the other half.
  • Extra space is $O(1)$ because it only keeps lo, hi and mid.

Self-check questions

Answer these in order, out loud. They follow the page, so your answers become the script of your video.

  1. How is peak finding like hiking in fog?
    Sample answer

    You can only see the next step, so you always step uphill. That guarantees you reach some summit, though not necessarily the highest one.

  2. Walk through peak finding on [2, 5, 7, 8, 6, 3, 1].
    Sample answer

    8 vs 6 is downhill, so keep 0..3; 5 vs 7 is uphill, so keep 2..3; 7 vs 8 is uphill, so keep 3..3. The peak is 8 at index 3.

  3. Why does the code set hi = mid and not hi = mid - 1 on the downhill branch?

    A good answer says what mid might be.

    Sample answer

    When a[mid] ≥ a[mid+1], mid itself might be the peak. Excluding it could throw away the only peak in the range.

  4. So what is peak finding, in one sentence?
    Sample answer

    It finds an element at least as big as its neighbours by repeatedly keeping the half on the uphill side of the middle.

  5. Why must the uphill side contain a peak?

    A good answer follows the rising values to their end.

    Sample answer

    Walk uphill from mid + 1. Either the values stop rising at some point, which is a peak, or they keep rising to the edge, and the edge element is a peak because outside is $-\infty$.

  6. What are its time and space complexity?
    Sample answer

    $O(\log n)$ comparisons in every case, since the loop always narrows to one element, and $O(1)$ extra space.

  7. Why can't the same trick find the maximum in $O(\log n)$?
    Sample answer

    The maximum could be in the half we throw away, as in [9, 1, 2, 3, 4, 5, 0]. Only a local peak is guaranteed on the uphill side, so the maximum still needs $O(n)$.

  8. If you had to teach this to a friend in 2 minutes, what would you leave out, and why?
    Sample answer

    I'd leave out the $-\infty$ boundary convention and the exact $\lceil \log_2 n \rceil$ count. I'd keep the fog analogy, the walk on the example, and "go uphill, halve each time, finds a peak, not the max".

External resources

Videos

Review schedule

Come back after 3, 7, 14, then 30 days. Spacing beats cramming.

Feynman prompt

Explain this topic in your own words, as if teaching a beginner.

Explain it in your own words first, then record a 2–3 minute video on your phone.

25:00

Ready to record? Open your phone camera now and record a 2–3 minute explanation.

My notes

Saved only in this browser (localStorage). Nothing is uploaded.