852. Peak Index in a Mountain Array
Problem Description
You are given an integer array arr
that represents a mountain array. A mountain array has the following properties:
- The array length
n
is at least 3 - The values strictly increase from the start until reaching a peak element
- After the peak, the values strictly decrease until the end
- There is exactly one peak element in the array
Your task is to find and return the index of the peak element (the highest value in the array).
For example, in the array [1, 3, 5, 4, 2]
, the peak element is 5
at index 2
.
Constraint: You must solve this problem in O(log(n))
time complexity, which means you cannot simply iterate through the array to find the maximum. Instead, you need to use a more efficient approach like binary search.
The solution uses binary search to efficiently locate the peak. It starts with left = 1
and right = len(arr) - 2
(excluding the first and last elements since they cannot be peaks). At each step, it calculates the middle index and compares arr[mid]
with arr[mid + 1]
:
- If
arr[mid] > arr[mid + 1]
, we're on the descending slope, so the peak is atmid
or to its left - If
arr[mid] < arr[mid + 1]
, we're on the ascending slope, so the peak is to the right ofmid
The search continues until left
and right
converge to the peak element's index.
Intuition
The key insight is recognizing that a mountain array has a special property: it's divided into two parts - an ascending part and a descending part, with a single peak between them. This structure naturally lends itself to binary search.
Think about what happens when we pick any element in the array and compare it with its neighbor:
- If
arr[mid] > arr[mid + 1]
, we know we're on the descending slope of the mountain. The peak must be at our current position or somewhere to the left. - If
arr[mid] < arr[mid + 1]
, we're still climbing up the mountain. The peak must be somewhere to the right.
This binary decision at each point allows us to eliminate half of the search space with each comparison, achieving the required O(log(n))
time complexity.
Why can we start with left = 1
and right = len(arr) - 2
? Because the first and last elements can never be peaks in a mountain array - the first element only has a neighbor to its right that must be greater, and the last element only has a neighbor to its left that must be greater.
The beauty of this approach is that we don't need to find the actual maximum value - we just need to find the point where the trend changes from increasing to decreasing. Once our search range narrows down to a single element (when left == right
), we've found our peak.
Learn more about Binary Search patterns.
Solution Approach
The solution implements a modified binary search algorithm to find the peak element in O(log(n))
time.
Implementation Steps:
-
Initialize search boundaries: Set
left = 1
andright = len(arr) - 2
. We exclude indices 0 andn-1
because they cannot be peaks in a mountain array by definition. -
Binary search loop: While
left < right
, we continue searching:- Calculate the middle index:
mid = (left + right) >> 1
(using bit shift for efficient division by 2) - Compare
arr[mid]
witharr[mid + 1]
to determine which side of the peak we're on
- Calculate the middle index:
-
Decision logic:
- If
arr[mid] > arr[mid + 1]
: We're on the descending part of the mountain. The peak is either atmid
or to its left. Updateright = mid
(keepingmid
in the search range since it could be the peak). - If
arr[mid] < arr[mid + 1]
: We're on the ascending part of the mountain. The peak must be to the right ofmid
. Updateleft = mid + 1
(excludingmid
since it can't be the peak).
- If
-
Return the result: When the loop exits,
left == right
, pointing to the peak element. Returnleft
.
Why this works:
- Each iteration eliminates half of the remaining search space
- The algorithm converges when the search range contains only one element
- This element must be the peak since we've systematically narrowed down from both sides
Time Complexity: O(log(n))
- we divide the search space in half with each iteration
Space Complexity: O(1)
- only using a constant amount of extra space for variables
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through finding the peak in the mountain array [1, 3, 5, 4, 2]
.
Initial Setup:
- Array:
[1, 3, 5, 4, 2]
(length = 5) left = 1
(index of 3)right = 3
(index of 4)- We're searching between indices 1 and 3, excluding the first and last elements
Iteration 1:
left = 1
,right = 3
- Calculate
mid = (1 + 3) >> 1 = 2
- Compare
arr[2]
witharr[3]
: Is 5 > 4? Yes - Since
arr[mid] > arr[mid + 1]
, we're on the descending slope - The peak is at index 2 or to its left
- Update:
right = mid = 2
Iteration 2:
left = 1
,right = 2
- Calculate
mid = (1 + 2) >> 1 = 1
- Compare
arr[1]
witharr[2]
: Is 3 > 5? No - Since
arr[mid] < arr[mid + 1]
, we're on the ascending slope - The peak must be to the right of index 1
- Update:
left = mid + 1 = 2
Termination:
- Now
left = 2
andright = 2
- The loop exits since
left == right
- Return
left = 2
The peak element is at index 2, which contains the value 5. Notice how we found it in just 2 comparisons instead of checking all 5 elements, demonstrating the efficiency of the binary search approach.
Solution Implementation
1class Solution:
2 def peakIndexInMountainArray(self, arr: List[int]) -> int:
3 """
4 Find the peak index in a mountain array using binary search.
5 A mountain array is an array that increases to a peak element and then decreases.
6
7 Args:
8 arr: List[int] - The mountain array
9
10 Returns:
11 int - The index of the peak element
12 """
13 # Initialize binary search boundaries
14 # Start from index 1 and end at len(arr) - 2 since the peak cannot be at the edges
15 left, right = 1, len(arr) - 2
16
17 # Binary search for the peak element
18 while left < right:
19 # Calculate middle index using bit shift (equivalent to dividing by 2)
20 mid = (left + right) >> 1
21
22 # Check if we're on the descending slope
23 if arr[mid] > arr[mid + 1]:
24 # Peak is at mid or to the left of mid
25 right = mid
26 else:
27 # We're on the ascending slope, peak is to the right of mid
28 left = mid + 1
29
30 # When left == right, we've found the peak index
31 return left
32
1class Solution {
2 /**
3 * Finds the peak index in a mountain array using binary search.
4 * A mountain array is defined as an array that:
5 * - arr.length >= 3
6 * - There exists some i with 0 < i < arr.length - 1 such that:
7 * - arr[0] < arr[1] < ... < arr[i - 1] < arr[i]
8 * - arr[i] > arr[i + 1] > ... > arr[arr.length - 1]
9 *
10 * @param arr the mountain array
11 * @return the index of the peak element
12 */
13 public int peakIndexInMountainArray(int[] arr) {
14 // Initialize binary search boundaries
15 // Start from index 1 and end at length-2 since peak cannot be at boundaries
16 int left = 1;
17 int right = arr.length - 2;
18
19 // Binary search for the peak element
20 while (left < right) {
21 // Calculate middle index using bit shift for efficiency
22 int mid = (left + right) >> 1;
23
24 // Check if we're on the descending slope
25 if (arr[mid] > arr[mid + 1]) {
26 // Peak is at mid or to the left of mid
27 right = mid;
28 } else {
29 // We're on the ascending slope, peak is to the right
30 left = mid + 1;
31 }
32 }
33
34 // When left equals right, we've found the peak
35 return left;
36 }
37}
38
1class Solution {
2public:
3 int peakIndexInMountainArray(vector<int>& arr) {
4 // Initialize binary search boundaries
5 // Start from index 1 and end at size-2 since the peak cannot be at the boundaries
6 int left = 1;
7 int right = arr.size() - 2;
8
9 // Binary search for the peak element
10 while (left < right) {
11 // Calculate middle index using bit shift (equivalent to dividing by 2)
12 int mid = (left + right) >> 1;
13
14 // If mid element is greater than the next element,
15 // we're on the descending part of the mountain or at the peak
16 // The peak must be at mid or to the left of mid
17 if (arr[mid] > arr[mid + 1]) {
18 right = mid;
19 }
20 // If mid element is less than or equal to the next element,
21 // we're on the ascending part of the mountain
22 // The peak must be to the right of mid
23 else {
24 left = mid + 1;
25 }
26 }
27
28 // When left == right, we've found the peak index
29 return left;
30 }
31};
32
1/**
2 * Finds the peak index in a mountain array using binary search.
3 * A mountain array is an array that increases to a peak element and then decreases.
4 * @param arr - The mountain array where arr.length >= 3
5 * @returns The index of the peak element
6 */
7function peakIndexInMountainArray(arr: number[]): number {
8 // Initialize binary search boundaries
9 // Start from index 1 and end at length-2 since peak cannot be at array boundaries
10 let left: number = 1;
11 let right: number = arr.length - 2;
12
13 // Binary search for the peak element
14 while (left < right) {
15 // Calculate middle index using bit shift for integer division
16 const mid: number = (left + right) >> 1;
17
18 // Check if mid is in the descending part of the mountain
19 if (arr[mid] > arr[mid + 1]) {
20 // Peak is at mid or to the left of mid
21 right = mid;
22 } else {
23 // Peak is to the right of mid
24 left = mid + 1;
25 }
26 }
27
28 // When left equals right, we've found the peak index
29 return left;
30}
31
Time and Space Complexity
Time Complexity: O(log n)
, where n
is the length of the input array. The algorithm uses binary search to find the peak element. In each iteration, the search space is reduced by half. Starting with n-2
elements (excluding the first and last elements), the algorithm performs at most log₂(n-2)
iterations, which simplifies to O(log n)
.
Space Complexity: 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 are created that scale with the input size.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Boundary Initialization
A frequent mistake is initializing the search boundaries as left = 0
and right = len(arr) - 1
, which includes the first and last elements. This can lead to incorrect results or index out of bounds errors when checking arr[mid + 1]
.
Why it's problematic:
- If
mid
equalslen(arr) - 1
, thenarr[mid + 1]
causes an IndexError - The first and last elements cannot be peaks in a valid mountain array by definition
Solution:
Always initialize with left = 1
and right = len(arr) - 2
to exclude edge elements.
2. Wrong Update Logic for Search Boundaries
Another common error is incorrectly updating the search boundaries, particularly setting right = mid - 1
when arr[mid] > arr[mid + 1]
.
Why it's problematic:
- When
arr[mid] > arr[mid + 1]
, we know we're on the descending slope, butmid
itself could be the peak - Setting
right = mid - 1
would exclude the potential peak from the search range
Solution:
Use right = mid
(not mid - 1
) when on the descending slope to keep mid
in the search range.
3. Confusion with Comparison Direction
Developers sometimes mix up the comparison logic, checking arr[mid] < arr[mid - 1]
instead of arr[mid] < arr[mid + 1]
.
Why it's problematic:
- Comparing with
arr[mid - 1]
requires different boundary logic and can complicate the algorithm - It may lead to incorrect identification of which slope we're on
Solution:
Consistently compare arr[mid]
with arr[mid + 1]
to determine if we're ascending or descending.
4. Handling Edge Cases Incorrectly
Not considering arrays with minimum length (n = 3) or assuming the peak could be at the boundaries.
Example of incorrect assumption:
# Wrong: Trying to handle peaks at boundaries
if arr[0] > arr[1]:
return 0
if arr[-1] > arr[-2]:
return len(arr) - 1
Solution: Trust the problem constraints - the peak is never at the boundaries in a valid mountain array, so no special edge case handling is needed.
In a binary min heap, the maximum element can be found in:
Recommended Readings
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
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 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
Want a Structured Path to Master System Design Too? Don’t Miss This!