Editorial
Core insight
Find in Mountain Array · Binary Search Patterns
Core Insight for Find in Mountain Array
The core difficulty in LeetCode 1095 is that the entire array is not sorted in a single direction. Standard binary search requires monotonic data (always increasing or always decreasing). A mountain array is bitonic: it increases, reaches a peak, and then decreases.
However, the peak element acts as a boundary.
- To the left of the peak (inclusive of the peak), the array is strictly ascending.
- To the right of the peak, the array is strictly descending.
If we can identify the index of the peak, we can treat the problem as two independent binary search problems on sorted arrays.
To find the peak efficiently, we utilize the derivative (slope) logic used in finding extrema. By comparing mid and mid + 1, we can determine if we are on the ascending slope or the descending slope:
- If
arr[mid] < arr[mid + 1], we are climbing up; the peak is to the right. - If
arr[mid] > arr[mid + 1], we are climbing down (or at the peak); the actual peak is atmidor to the left.
Visual Description: Imagine plotting the array values on a graph where the Y-axis is the value and X-axis is the index. The shape forms an inverted "V".
- Phase 1 (Peak Finding): We probe the middle of the range. If the slope at that point is positive, we discard the left half. If the slope is negative, we discard the right half. This narrows the search space to the single highest point (the peak).
- Phase 2 (Target Search): Once the peak index is fixed, the graph is split into a "Left Wing" (strictly increasing) and a "Right Wing" (strictly decreasing). We perform binary search on the Left Wing first. If the target is found, we return that index (since we need the minimum index). If not, we perform binary search on the Right Wing.
Deep dive
Full breakdown
Full reasoning, pitfalls, and implementation flow
LeetCode 1095: Find in Mountain Array Solution & Explanation
Problem Overview
TL;DR: We solve this by first locating the peak element's index using binary search, then performing two separate binary searches on the ascending and descending slopes to find the target.
The Find in Mountain Array problem asks us to search for a specific target value within a "mountain array." A mountain array is defined as an array that strictly increases to a peak element and then strictly decreases. We are not given direct access to the array; instead, we must interact with it via a MountainArray interface that allows us to retrieve the length and the value at a specific index.
The critical constraint in LeetCode 1095 is the limit on API calls: we are allowed a maximum of 100 calls to get(k). Since the array length can be up to 10,000, a linear scan is impossible, necessitating a logarithmic time complexity solution.
Brute Force Approach for Find in Mountain Array
The most straightforward approach to finding an element in an array is a linear scan. We would simply iterate through every index from 0 to n-1, call get(i), and check if the value equals the target.
Naive Pseudo-code:
function findInMountainArray(target, mountainArr):
length = mountainArr.length()
for i from 0 to length - 1:
val = mountainArr.get(i)
if val == target:
return i
return -1Complexity Analysis:
- Time Complexity: , where is the length of the mountain array.
- Space Complexity: .
Why it fails:
The problem strictly enforces an interaction limit. With up to , a linear scan requires up to 10,000 calls to MountainArray.get(). The limit is 100 calls. Since , this approach will immediately trigger a "Wrong Answer" or disqualification due to excessive API usage. We must minimize calls using an strategy.
Algorithm Strategy: Binary Search Patterns
Our optimal strategy involves three distinct binary search operations performed in sequence. This ensures we stay well within the 100-call limit, as calls max.
-
Find the Peak Index:
- Use the logic from the "Find Min/Max" subpattern.
- Maintain
lowandhighpointers. - Compare
get(mid)withget(mid + 1). - If
get(mid) < get(mid + 1), the peak is strictly to the right (low = mid + 1). - Otherwise, the peak is at
midor to the left (high = mid). - The loop terminates when
low == high, giving us thepeakIndex.
-
Search the Ascending Part:
- Perform a standard "Order-Agnostic" binary search on the range
[0, peakIndex]. - Since this segment is sorted in ascending order, if
get(mid) < target, we move right. Ifget(mid) > target, we move left. - If found, return the index immediately (this guarantees the minimum index).
- Perform a standard "Order-Agnostic" binary search on the range
-
Search the Descending Part:
- If the target wasn't found in the ascending part, perform binary search on the range
[peakIndex + 1, length - 1]. - Note that this segment is sorted in descending order.
- Logic is inverted: if
get(mid) < target, we move left (towards larger values). Ifget(mid) > target, we move right. - If found, return the index. Otherwise, return
-1.
- If the target wasn't found in the ascending part, perform binary search on the range
Execution Flow
Let's trace the algorithm with mountainArr = [1, 2, 3, 4, 5, 3, 1] and target = 3.
Step 1: Find Peak Index
- Range
[0, 6].mid = 3. - Compare
arr[3](4) andarr[4](5). - Since , we are ascending. Peak is to the right. New range
[4, 6]. mid = 5. Comparearr[5](3) andarr[6](1).- Since , we are descending. Peak is at or left of 5. New range
[4, 5]. mid = 4. Comparearr[4](5) andarr[5](3).- Since , we are descending. Peak is at or left of 4. New range
[4, 4]. low == high. Peak Index is 4 (Value is 5).
Step 2: Search Left (Ascending) [0, 4]
- Range
[0, 4]. Target is 3. mid = 2.arr[2]is 3.- Match found!
- Since we want the minimum index and we search the left side first, we return
2.
Note: If the target were 3 in the right half only (e.g., if the array were [1, 5, 3]), Step 2 would fail, and we would proceed to Step 3.
Proof of Correctness
The correctness relies on the Bitonic Property of the Mountain Array.
- Peak Uniqueness: A mountain array has exactly one peak. The condition
arr[i] < arr[i+1]is true for all and false for all . This monotonicity in the slope allows binary search to converge on the peak index correctly. - Coverage: By splitting the array at the peak, we cover the entire search space. The left side
[0, peak]is strictly increasing, and the right side[peak+1, end]is strictly decreasing. - Min Index Requirement: Searching the left subarray first ensures that if the target appears twice (once on each slope), we return the smaller index.
Pattern Reuse Notes
The logic used in Find in Mountain Array is a specific application of binary search on modified sorted arrays. This pattern recurs in several famous interview questions:
- LeetCode 33: Search in Rotated Sorted Array: Similar to the mountain array, a rotated array consists of two sorted segments. You must determine which segment is sorted to decide pointer movement.
- LeetCode 81: Search in Rotated Sorted Array II: Handles duplicates in the rotated array context, requiring worst-case fallback but utilizing similar pivot logic.
- LeetCode 153: Find Minimum in Rotated Sorted Array: This is the direct inverse of finding the peak. You use binary search to find the point where the order breaks (the minimum).
- LeetCode 162: Find Peak Element: This is exactly the first step of the Mountain Array solution. It asks you to find any peak in an unsorted array where adjacent elements are never equal.
Feynman coach · Before you peek at the code
Can you explain the core insight in your own words?
If you can explain it before reading code, you’re far more likely to recall it under interview pressure.
Reference Implementation
C++ Solution for LeetCode 1095
1/**
2 * // This is the MountainArray's API interface.
3 * // You should not implement it, or speculate about its implementation
4 * class MountainArray {
5 * public:
6 * int get(int index);
7 * int length();
8 * };
9 */
10
11class Solution {
12public:
13 int findInMountainArray(int target, MountainArray &mountainArr) {
14 int n = mountainArr.length();
15
16 // 1. Find the peak index
17 int low = 0;
18 int high = n - 1;
19 while (low < high) {
20 int mid = low + (high - low) / 2;
21 if (mountainArr.get(mid) < mountainArr.get(mid + 1)) {
22 // Ascending part, peak is to the right
23 low = mid + 1;
24 } else {
25 // Descending part or peak, peak is left or here
26 high = mid;
27 }
28 }
29 int peakIndex = low;
30
31 // 2. Search in the ascending part (left of peak)
32 low = 0;
33 high = peakIndex;
34 while (low <= high) {
35 int mid = low + (high - low) / 2;
36 int val = mountainArr.get(mid);
37 if (val == target) return mid;
38 if (val < target) {
39 low = mid + 1;
40 } else {
41 high = mid - 1;
42 }
43 }
44
45 // 3. Search in the descending part (right of peak)
46 low = peakIndex + 1;
47 high = n - 1;
48 while (low <= high) {
49 int mid = low + (high - low) / 2;
50 int val = mountainArr.get(mid);
51 if (val == target) return mid;
52 if (val < target) {
53 // In descending order, smaller value means we are too far right
54 high = mid - 1;
55 } else {
56 // Larger value means we are too far left
57 low = mid + 1;
58 }
59 }
60
61 return -1;
62 }
63};Java Solution for LeetCode 1095
1/**
2 * // This is MountainArray's API interface.
3 * // You should not implement it, or speculate about its implementation
4 * interface MountainArray {
5 * public int get(int index);
6 * public int length();
7 * }
8 */
9
10class Solution {
11 public int findInMountainArray(int target, MountainArray mountainArr) {
12 int length = mountainArr.length();
13
14 // 1. Find the peak index
15 int low = 0;
16 int high = length - 1;
17 while (low < high) {
18 int mid = low + (high - low) / 2;
19 if (mountainArr.get(mid) < mountainArr.get(mid + 1)) {
20 // We are in the increasing section
21 low = mid + 1;
22 } else {
23 // We are in the decreasing section or at the peak
24 high = mid;
25 }
26 }
27 int peakIndex = low;
28
29 // 2. Binary Search on the increasing subarray [0, peakIndex]
30 int result = binarySearch(mountainArr, target, 0, peakIndex, true);
31 if (result != -1) {
32 return result;
33 }
34
35 // 3. Binary Search on the decreasing subarray [peakIndex + 1, length - 1]
36 return binarySearch(mountainArr, target, peakIndex + 1, length - 1, false);
37 }
38
39 private int binarySearch(MountainArray arr, int target, int low, int high, boolean ascending) {
40 while (low <= high) {
41 int mid = low + (high - low) / 2;
42 int val = arr.get(mid);
43
44 if (val == target) {
45 return mid;
46 }
47
48 if (ascending) {
49 if (val < target) {
50 low = mid + 1;
51 } else {
52 high = mid - 1;
53 }
54 } else {
55 // Descending logic
56 if (val < target) {
57 high = mid - 1; // Target is larger, move left
58 } else {
59 low = mid + 1; // Target is smaller, move right
60 }
61 }
62 }
63 return -1;
64 }
65}Python Solution for LeetCode 1095
1# """
2# This is MountainArray's API interface.
3# You should not implement it, or speculate about its implementation
4# """
5#class MountainArray:
6# def get(self, index: int) -> int:
7# def length(self) -> int:
8
9class Solution:
10 def findInMountainArray(self, target: int, mountain_arr: 'MountainArray') -> int:
11 n = mountain_arr.length()
12
13 # 1. Find the peak index
14 low, high = 0, n - 1
15 while low < high:
16 mid = (low + high) // 2
17 if mountain_arr.get(mid) < mountain_arr.get(mid + 1):
18 low = mid + 1
19 else:
20 high = mid
21 peak_index = low
22
23 # 2. Search in the ascending part
24 low, high = 0, peak_index
25 while low <= high:
26 mid = (low + high) // 2
27 val = mountain_arr.get(mid)
28 if val == target:
29 return mid
30 elif val < target:
31 low = mid + 1
32 else:
33 high = mid - 1
34
35 # 3. Search in the descending part
36 low, high = peak_index + 1, n - 1
37 while low <= high:
38 mid = (low + high) // 2
39 val = mountain_arr.get(mid)
40 if val == target:
41 return mid
42 elif val < target:
43 # In descending, smaller value means go left (to larger values)
44 high = mid - 1
45 else:
46 low = mid + 1
47
48 return -1