Facebook Pixel

162. Find Peak Element

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?

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).

Due to the boundary conditions (nums[-1] = nums[n] = -∞), this creates an interesting pattern. At some point, we must transition from "going up" to "going down" (a peak). The pattern looks like:

For array [1, 2, 3, 1]:

  • feasible(0) = (1 > 2) → false (going up)
  • feasible(1) = (2 > 3) → false (going up)
  • feasible(2) = (3 > 1) → true (going down, this is a peak!)

The first true position is a peak element because:

  • If feasible(i) = true, then nums[i] > nums[i + 1]
  • Since feasible(i-1) = false (or i=0 with boundary -∞), then nums[i-1] < nums[i]
  • Therefore nums[i] is greater than both neighbors

Using the standard binary search template, we find the first index where nums[mid] > nums[mid + 1] is true. This gives us a peak element.

The boundary conditions of negative infinity ensure that no matter what, at least one peak must exist, so first_true_index will always be set.

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

Solution Approach

We use the standard binary search template to find the first index where nums[mid] > nums[mid + 1] is true.

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]

This creates a pattern where the first true position is a peak element.

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

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example 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 template finds the leftmost peak after our first discovered feasible point.

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: Using while left < right with right = mid instead of the standard template:

# INCORRECT variant
while left < right:
    mid = (left + right) // 2
    if nums[mid] > nums[mid + 1]:
        right = mid  # Wrong variant!
    else:
        left = mid + 1
return left

Correct Template Implementation:

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 because the feasible pattern [false, ..., true, ...] maps to "going up, ..., going down, ...". The first true is where we transition from ascending to descending - which is exactly a peak.

Loading...
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

You are given an array of intervals where intervals[i] = [start_i, end_i] represent the start and end of the ith interval. You need to merge all overlapping intervals and return an array of the non-overlapping intervals that cover all the intervals in the input.


Recommended Readings

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

Load More