Find Peak Element
MedPrompt
A peak element is an element that is strictly greater than its neighbors.
Given a 0-indexed integer array nums, find a peak element, and return its index. If the array contains multiple peaks, return the index to any of the peaks.
You may imagine that nums[-1] = nums[n] = -∞. In other words, an element is always considered to be strictly greater than a neighbor that is outside the array.
You must write an algorithm that runs in O(log n) time.
Example 1:
Input: nums = [1,2,3,1]
Output: 2
Explanation: 3 is a peak element and your function should return the index number 2.Example 2:
Input: nums = [1,2,1,3,5,6,4]
Output: 5
Explanation: Your function can return either index number 1 where the peak element is 2, or index number 5 where the peak element is 6.
Constraints:
1 <= nums.length <= 1000-231 <= nums[i] <= 231 - 1nums[i] != nums[i + 1]for all validi.
Approaches
2 approaches with complexity analysis and trade-offs.
This approach involves a simple linear scan through the array. We iterate over each element and check if it satisfies the condition of being a peak: being strictly greater than its adjacent neighbors. Special care is taken for the first and last elements of the array, as they only have one neighbor to compare against.
Algorithm
- Handle the edge case where the array has only one element by returning index 0.
- Check if the first element
nums[0]is a peak. This is true ifnums[0] > nums[1]. If so, return 0. - Check if the last element
nums[n-1]is a peak. This is true ifnums[n-1] > nums[n-2]. If so, returnn-1. - Iterate through the rest of the array from index
i = 1ton-2. - For each element
nums[i], check if it is strictly greater than both its left neighbornums[i-1]and its right neighbornums[i+1]. - If the condition
nums[i] > nums[i-1] && nums[i] > nums[i+1]is met,nums[i]is a peak. Return its indexi. - Since the problem guarantees a peak exists, the function will always return an index from one of these checks.
Walkthrough
The brute-force method is to iterate through the nums array and check each element to see if it's a peak. The problem statement implies that nums[-1] and nums[n] are negative infinity, which simplifies the logic for the boundary elements.
- Single Element Array: If the array contains only one element, that element is a peak by definition. We return index 0.
- First Element: We check if
nums[0]is greater thannums[1]. If it is,nums[0]is a peak. - Last Element: We check if
nums[n-1]is greater thannums[n-2]. If it is,nums[n-1]is a peak. - Middle Elements: We loop from the second element to the second-to-last element. For each element
nums[i], we check if it's greater than bothnums[i-1]andnums[i+1].
Since the problem guarantees that a peak always exists, this scan will find and return the index of one of the peaks.
class Solution { public int findPeakElement(int[] nums) { int n = nums.length; if (n == 1) { return 0; } // Check if the first element is a peak if (nums[0] > nums[1]) { return 0; } // Check if the last element is a peak if (nums[n - 1] > nums[n - 2]) { return n - 1; } // Check the elements in the middle for (int i = 1; i < n - 1; i++) { if (nums[i] > nums[i - 1] && nums[i] > nums[i + 1]) { return i; } } return -1; // This line is unreachable given the problem constraints }}Complexity
Time
O(n)
Space
O(1)
Trade-offs
Pros
The logic is straightforward and easy to understand.
It's simple to implement.
Cons
The time complexity of O(n) does not meet the problem's requirement of an O(log n) solution.
It is inefficient for large input arrays as it may need to scan the entire array in the worst-case scenario (e.g., a sorted array where the peak is the last element).
The O(log n) time complexity requirement strongly suggests a binary search-based approach. Although the array is not sorted, we can use the properties of a peak to our advantage. By comparing the middle element with its neighbor, we can determine whether a peak is guaranteed to exist in the left or right half of the current search space, allowing us to eliminate half of the elements in each step.
Algorithm
- Initialize two pointers,
left = 0andright = nums.length - 1. - Enter a loop that continues as long as
left < right. - Calculate the middle index
mid = left + (right - left) / 2. - Compare the element at the middle index,
nums[mid], with its right neighbor,nums[mid + 1]. - If
nums[mid] < nums[mid + 1], it means we are on an 'uphill' slope. A peak must exist to the right ofmid. So, we can discard the left half of the search space by settingleft = mid + 1. - If
nums[mid] >= nums[mid + 1], it means we are on a 'downhill' slope. The elementnums[mid]could be a peak, or a peak exists to its left. So, we are certain a peak lies in the left half (includingmid). We discard the right half by settingright = mid. - The loop terminates when
leftandrightpointers converge (left == right). This final index is guaranteed to be a peak.
Walkthrough
This optimal approach uses a modified binary search algorithm. The key idea is that for any given element nums[mid], we can decide which side of it must contain a peak.
We maintain a search space defined by left and right pointers. In each iteration, we examine nums[mid] and its right neighbor nums[mid + 1]:
-
Case 1:
nums[mid] < nums[mid + 1]This indicates that we are on an upward slope. Since we know the array must eventually go down (asnums[n] = -∞), there must be a peak somewhere to the right ofmid. Therefore, we can safely discard the left part of the array, includingmid, and continue our search in the range[mid + 1, right]. -
Case 2:
nums[mid] > nums[mid + 1]This indicates that we are on a downward slope.nums[mid]itself might be a peak, or there might be an even higher peak to its left (sincenums[-1] = -∞). In either scenario, a peak is guaranteed to exist in the range[left, mid]. We can discard the right part and continue our search in this new range.
The loop continues until the search space is narrowed down to a single element (left == right). This single element is guaranteed to be a peak.
class Solution { public int findPeakElement(int[] nums) { int left = 0; int right = nums.length - 1; while (left < right) { int mid = left + (right - left) / 2; if (nums[mid] < nums[mid + 1]) { // Peak is in the right half left = mid + 1; } else { // Peak is in the left half, including mid right = mid; } } // At the end of the loop, left == right, which is the index of a peak return left; }}Complexity
Time
O(log n)
Space
O(1)
Trade-offs
Pros
Highly efficient, with a time complexity of O(log n), which meets the problem's constraints.
The space complexity is constant, O(1).
It is the optimal solution for this problem.
Cons
The underlying logic is more complex and less intuitive than a simple linear scan.
Requires careful handling of pointers and loop conditions to ensure correctness.
Solutions
Solution
class Solution { public int findPeakElement ( int [] nums ) { int left = 0 , right = nums . length - 1 ; while ( left < right ) { int mid = ( left + right ) >> 1 ; if ( nums [ mid ] > nums [ mid + 1 ]) { right = mid ; } else { left = mid + 1 ; } } return left ; } }class Solution { public: int findPeakElement ( vector < int >& nums ) { int left = 0 , right = nums . size () - 1 ; while ( left < right ) { int mid = left + right >> 1 ; if ( nums [ mid ] > nums [ mid + 1 ]) { right = mid ; } else { left = mid + 1 ; } } return left ; } };class Solution : def findPeakElement ( self , nums : List [ int ]) -> int : left , right = 0 , len ( nums ) - 1 while left < right : mid = ( left + right ) >> 1 if nums [ mid ] > nums [ mid + 1 ]: right = mid else : left = mid + 1 return leftVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.