Facebook Pixel

162. Find Peak Element

LeetCode ↗

Problem Description

You need to find a peak element in an array. A peak element is defined as an element that is strictly greater than both of its neighboring elements.

Given a 0-indexed integer array nums, your task is to find any peak element and return its index. If there are multiple peaks in the array, you can return the index of any one of them.

Important constraints:

  • Elements at the boundaries have special conditions: you can imagine that nums[-1] = nums[n] = -∞ (negative infinity), where n is the length of the array
  • This means the first element only needs to be greater than the second element to be a peak (since its left neighbor is considered as negative infinity)
  • Similarly, the last element only needs to be greater than the second-to-last element to be a peak

Time complexity requirement: Your algorithm must run in O(log n) time, which suggests using a binary search approach rather than a linear scan.

Example scenarios:

  • In an array like [1, 2, 3, 1], the element 3 at index 2 is a peak because 3 > 2 and 3 > 1
  • In an array like [1, 2, 1, 3, 5, 6, 4], there are two peaks: index 1 (value 2) and index 5 (value 6). You can return either index

The key insight is that due to the boundary conditions and the definition of a peak, at least one peak element is guaranteed to exist in any non-empty array.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

How We Pick the Algorithm

Why Binary Search?

This problem maps to Binary Search through a short path in the full flowchart.

Graph?noSorted ormonotonic?yesBinary Search

Binary search on the slope: move toward whichever neighbor is larger, since a peak must exist on that side.

Open in Flowchart

Intuition

The key observation is that we need O(log n) time complexity, which immediately suggests binary search. But how can we apply binary search when the array isn't sorted?

We can define a feasible function: feasible(i) = nums[i] > nums[i + 1]. This determines whether index i is on a "downward slope" (going from current to next element).

Unlike a sorted array, this condition does not split the array into all false followed by all true. For [1, 2, 1, 3, 5, 6, 4] the values are false, true, false, false, false, true, true (the last index counts as true because nums[n] = -∞). Binary search still works, because of a different guarantee:

  • If feasible(mid) is false, then nums[mid] < nums[mid + 1]. Walking right from mid + 1, the values must eventually stop rising (the array ends at -∞), so a peak exists somewhere in [mid + 1, right]. Moving left = mid + 1 keeps that peak in range, and it also means nums[left - 1] < nums[left] from then on.
  • If feasible(mid) is true, then nums[mid] > nums[mid + 1]. Walking left from mid, the values must eventually stop rising in that direction (the array starts at -∞), so a peak exists in [left, mid]. We record mid and keep looking to its left.

When the loop ends, left sits on the last recorded index first_true_index. Its right side is lower because it was feasible, and its left side is lower because of the nums[left - 1] < nums[left] fact above (or because it is index 0). So that index is a peak, even though it may not be the leftmost peak.

For [1, 2, 3, 1], feasible is false, false, true, true, and the search returns index 2.

Because the last index always counts as feasible, first_true_index is always set.

Watch it run

Step through it below. Each midpoint's slope decides which way a peak must lie.

New to patterns?Most interview problems reduce to a handful of patterns. This video covers the main eight.

Solution Approach

We use the standard binary search template with the condition nums[mid] > nums[mid + 1], keeping the last index where it held.

Initial Setup:

left, right = 0, len(nums) - 1
first_true_index = -1

Initialize the search boundaries and a variable to track the first position where the feasible condition is true.

Feasible Function: For any index mid, the feasible condition is: nums[mid] > nums[mid + 1]

The last index recorded by this search is always a peak (see the intuition above for why).

Binary Search Loop: Using while left <= right:

  1. Calculate the middle index: mid = (left + right) // 2

  2. Check if feasible (nums[mid] > nums[mid + 1]):

    • If true: This could be a peak (or there's a peak to the left). Record first_true_index = mid, then search left with right = mid - 1
    • If false: We're on an upward slope, so the peak must be to the right. Search right with left = mid + 1

Edge Case - Last Element: When mid == n - 1, there's no nums[mid + 1] to compare. However, since nums[n] = -∞ by definition, nums[n-1] > nums[n] is always true. We handle this by treating mid == n - 1 as feasible.

Return Result: After the loop, first_true_index holds the index of a peak element. Return first_true_index.

Time Complexity: O(log n) - We halve the search space in each iteration Space Complexity: O(1) - We only use a constant amount of extra space for the pointers

Example Walkthrough

Prefer text? Read the step-by-step walkthrough.

Let's walk through the algorithm with the array nums = [1, 2, 1, 3, 5, 6, 4].

Setup:

  • Array: [1, 2, 1, 3, 5, 6, 4]
  • left = 0, right = 6
  • first_true_index = -1
  • Feasible condition: nums[mid] > nums[mid + 1]

Iteration 1:

  • left = 0, right = 6
  • mid = (0 + 6) // 2 = 3
  • Check nums[3] = 3 > nums[4] = 5? No (not feasible, going up)
  • Update: left = mid + 1 = 4

Iteration 2:

  • left = 4, right = 6
  • mid = (4 + 6) // 2 = 5
  • Check nums[5] = 6 > nums[6] = 4? Yes (feasible, going down)
  • Update: first_true_index = 5, right = mid - 1 = 4

Iteration 3:

  • left = 4, right = 4
  • mid = (4 + 4) // 2 = 4
  • Check nums[4] = 5 > nums[5] = 6? No (not feasible, going up)
  • Update: left = mid + 1 = 5

Loop ends (left > right)

Result:

  • first_true_index = 5
  • Return 5
  • Verify: nums[5] = 6 is indeed a peak because 6 > 5 (left neighbor) and 6 > 4 (right neighbor)

Feasibility pattern for this array:

Index:      0      1      2      3      4      5      6
Value:      1      2      1      3      5      6      4
> next?:  false  true  false  false  false  true   true(boundary)

Note that the algorithm found index 5 as a peak, but index 1 (value 2) is also a valid peak. The search is not guaranteed to return the leftmost peak, only some peak, which the problem allows.

Solution Implementation

1class Solution:
2    def findPeakElement(self, nums: List[int]) -> int:
3        n = len(nums)
4        left, right = 0, n - 1
5        first_true_index = -1
6
7        # Binary search using the template: find first index where nums[mid] > nums[mid + 1]
8        while left <= right:
9            mid = (left + right) // 2
10
11            # Feasible condition: nums[mid] > nums[mid + 1]
12            # For last element, treat as feasible (nums[n] = -infinity)
13            if mid == n - 1 or nums[mid] > nums[mid + 1]:
14                first_true_index = mid
15                right = mid - 1
16            else:
17                left = mid + 1
18
19        # first_true_index points to a peak element
20        return first_true_index
21
1class Solution {
2    public int findPeakElement(int[] nums) {
3        int n = nums.length;
4        int left = 0;
5        int right = n - 1;
6        int firstTrueIndex = -1;
7
8        // Binary search using the template: find first index where nums[mid] > nums[mid + 1]
9        while (left <= right) {
10            int mid = left + (right - left) / 2;
11
12            // Feasible condition: nums[mid] > nums[mid + 1]
13            // For last element, treat as feasible (nums[n] = -infinity)
14            if (mid == n - 1 || nums[mid] > nums[mid + 1]) {
15                firstTrueIndex = mid;
16                right = mid - 1;
17            } else {
18                left = mid + 1;
19            }
20        }
21
22        // firstTrueIndex points to a peak element
23        return firstTrueIndex;
24    }
25}
26
1class Solution {
2public:
3    int findPeakElement(vector<int>& nums) {
4        int n = nums.size();
5        int left = 0;
6        int right = n - 1;
7        int firstTrueIndex = -1;
8
9        // Binary search using the template: find first index where nums[mid] > nums[mid + 1]
10        while (left <= right) {
11            int mid = left + (right - left) / 2;
12
13            // Feasible condition: nums[mid] > nums[mid + 1]
14            // For last element, treat as feasible (nums[n] = -infinity)
15            if (mid == n - 1 || nums[mid] > nums[mid + 1]) {
16                firstTrueIndex = mid;
17                right = mid - 1;
18            } else {
19                left = mid + 1;
20            }
21        }
22
23        // firstTrueIndex points to a peak element
24        return firstTrueIndex;
25    }
26};
27
1function findPeakElement(nums: number[]): number {
2    const n: number = nums.length;
3    let left: number = 0;
4    let right: number = n - 1;
5    let firstTrueIndex: number = -1;
6
7    // Binary search using the template: find first index where nums[mid] > nums[mid + 1]
8    while (left <= right) {
9        const mid: number = Math.floor((left + right) / 2);
10
11        // Feasible condition: nums[mid] > nums[mid + 1]
12        // For last element, treat as feasible (nums[n] = -infinity)
13        if (mid === n - 1 || nums[mid] > nums[mid + 1]) {
14            firstTrueIndex = mid;
15            right = mid - 1;
16        } else {
17            left = mid + 1;
18        }
19    }
20
21    // firstTrueIndex points to a peak element
22    return firstTrueIndex;
23}
24

Time and Space Complexity

The time complexity is O(log n), where n is the length of the array nums. This is because the algorithm uses binary search, which divides the search space in half with each iteration. In each iteration, we compare the middle element with its neighbor and decide which half of the array to continue searching. Since we reduce the problem size by half each time, the number of iterations is at most log₂(n), resulting in O(log n) time complexity.

The space complexity is O(1). The algorithm only uses a constant amount of extra space for the variables left, right, and mid, regardless of the input size. No additional data structures or recursive calls are used that would scale with the input size.

Common Pitfalls

1. Using Wrong Binary Search Template Variant

The Pitfall: Two correct forms are common: while left < right with right = mid, and while left <= right with right = mid - 1 plus a saved answer. Mixing them breaks the search, for example while left <= right with right = mid, which never ends once left == right and mid is feasible.

Correct Variant A (shrinking window):

while left < right:  # mid < right here, so nums[mid + 1] always exists
    mid = (left + right) // 2
    if nums[mid] > nums[mid + 1]:
        right = mid
    else:
        left = mid + 1
return left

Correct Variant B (template used in this solution):

first_true_index = -1
while left <= right:
    mid = (left + right) // 2
    if mid == n - 1 or nums[mid] > nums[mid + 1]:
        first_true_index = mid
        right = mid - 1
    else:
        left = mid + 1
return first_true_index

2. Forgetting the Last Element Edge Case

The Pitfall: When mid == n - 1, there's no nums[mid + 1] to compare, causing an index out of bounds error.

Incorrect approach:

if nums[mid] > nums[mid + 1]:  # IndexError when mid == n - 1
    first_true_index = mid
    right = mid - 1

Correct approach:

# Handle last element separately (always feasible since nums[n] = -infinity)
if mid == n - 1 or nums[mid] > nums[mid + 1]:
    first_true_index = mid
    right = mid - 1

3. Integer Overflow in Mid Calculation

The Pitfall: Using (left + right) / 2 in languages with fixed integer sizes:

// INCORRECT - can overflow
int mid = (left + right) / 2;

Solution: Use overflow-safe calculation:

int mid = left + (right - left) / 2;

4. Comparing with Both Neighbors

The Pitfall: Checking both neighbors complicates the logic unnecessarily:

# Overly complex
if mid > 0 and mid < n - 1:
    if nums[mid] > nums[mid - 1] and nums[mid] > nums[mid + 1]:
        return mid

Better approach: Just compare with mid + 1 - the binary search guarantees we find a peak by following the feasible condition.

5. Not Understanding Why the Template Works Here

The template works even though the feasible values are not sorted: every false at mid guarantees a peak to the right, every true guarantees a peak at or to the left, so the range that is kept always contains a peak.

Ready to land your dream job?

Unlock your dream job with a 5-minute quiz for a personalized study roadmap!

Get My Roadmap
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Get a Personalized Study Roadmap:

Which of the two traversal algorithms (BFS and DFS) can be used to find whether two nodes are connected?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More