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), wherenis 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 element3at index2is a peak because3 > 2and3 > 1 - In an array like
[1, 2, 1, 3, 5, 6, 4], there are two peaks: index1(value2) and index5(value6). 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.
How We Pick the Algorithm
Why Binary Search?
This problem maps to Binary Search through a short path in the full flowchart.
Binary search on the slope: move toward whichever neighbor is larger, since a peak must exist on that side.
Open in FlowchartIntuition
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)isfalse, thennums[mid] < nums[mid + 1]. Walking right frommid + 1, the values must eventually stop rising (the array ends at-∞), so a peak exists somewhere in[mid + 1, right]. Movingleft = mid + 1keeps that peak in range, and it also meansnums[left - 1] < nums[left]from then on. - If
feasible(mid)istrue, thennums[mid] > nums[mid + 1]. Walking left frommid, the values must eventually stop rising in that direction (the array starts at-∞), so a peak exists in[left, mid]. We recordmidand 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.
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:
-
Calculate the middle index:
mid = (left + right) // 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 withright = mid - 1 - If false: We're on an upward slope, so the peak must be to the right. Search right with
left = mid + 1
- If true: This could be a peak (or there's a peak to the left). Record
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 = 6first_true_index = -1- Feasible condition:
nums[mid] > nums[mid + 1]
Iteration 1:
left = 0,right = 6mid = (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 = 6mid = (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 = 4mid = (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] = 6is indeed a peak because6 > 5(left neighbor) and6 > 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
211class 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}
261class 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};
271function 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}
24Time 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 RoadmapWhich of the two traversal algorithms (BFS and DFS) can be used to find whether two nodes are connected?
Recommended Readings
https assets algo monster cover_photos Binary_Search svg Binary Search Intuition Binary search is an efficient array search algorithm It works by narrowing down the search range by half each time If you have looked up a word in a physical dictionary you've already used binary search in real life Let's
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion If you prefer videos here's a video that explains recursion in a fun and easy way Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask him
Want a Structured Path to Master System Design Too? Don’t Miss This!