Find a value in a mountain array
A mountain array strictly increases to a single peak and then strictly decreases. Given a
mountain array a and a target, return the smallest index i with
a[i] = target, or −1 if there is none. Reading an element is expensive: use
O(log n) reads.
Examples
| Input | Output | Why |
|---|---|---|
a = [1, 2, 3, 4, 5, 3, 1], target = 3 | 2 | 3 appears at indices 2 and 5; the smaller is 2. |
a = [0, 1, 2, 4, 2, 1], target = 3 | -1 | |
a = [1, 5, 2], target = 2 | 2 |
Constraints
- 3 ≤ n ≤ 10⁴
- 0 ≤ a[i], target ≤ 10⁹
- a is a mountain array
Hints
Hint 1
A value can appear at most twice: once on the way up and once on the way down.
Hint 2
Find the peak first with the slope test from the previous problem.
Hint 3
Then run a normal lower-bound search on a[0..peak] (ascending). Only if that fails, search a[peak + 1..n − 1], which is descending, so the comparison flips.
Where people lose marks
- Searching the falling side with the ascending comparison.
- Searching the falling side first, which returns the larger of two indices.
- Including the peak in both side searches, then reading past the end when p = n − 1 (impossible for a mountain, but cheap to guard).
Dry run on [1,4,8,12,17,15,10,6,2],6
Read it one frame at a time, the way you would trace the code on paper in an interview. Each frame shows the state after the step it describes.
-
Step 1 / 11target6
Find 6. First the peak, then the rising side, then, if needed, the falling side.
-
Step 2 / 11searchpeaklo0mid4hi8
Peak search: a[4] = 17 > a[5] = 15, going down: hi = 4.
-
Step 3 / 11searchpeaklo0mid2hi4
Peak search: a[2] = 8 < a[3] = 12, still climbing: lo = 3.
-
Step 4 / 11searchpeaklo3mid3hi4
Peak search: a[3] = 12 < a[4] = 17, still climbing: lo = 4.
-
Step 5 / 11peak4
The peak is at 4. The rising side is [0, 4], the falling side [5, 8].
-
Step 6 / 11searchrisinglo0mid2hi4
Rising side: a[2] = 8 ≥ 6: hi = 2.
-
Step 7 / 11searchrisinglo0mid1hi2
Rising side: a[1] = 4 < 6: lo = 2.
-
Step 8 / 11
The rising side stops at index 2, which holds 8, not 6. Try the falling side, where the test flips to a[i] ≤ 6.
-
Step 9 / 11searchfallinglo5mid6hi8
Falling side: a[6] = 10 > 6: lo = 7.
-
Step 10 / 11searchfallinglo7mid7hi8
Falling side: a[7] = 6 ≤ 6: hi = 7.
-
Step 11 / 11
a[7] = 6. Return 7.
How to think about it
A mountain array is two sorted arrays glued at the peak: a[0..p] ascending and
a[p..n − 1] descending. Each side holds every value at most once, so the target appears at most
twice, and the smaller index is on the rising side if it is there at all.
- Find the peak
p: the first index witha[i] > a[i + 1]. - Search the rising side
[0, p]for the first index witha[i] ≥ target. If it holdstarget, return it. - Search the falling side
[p + 1, n − 1]for the first index witha[i] ≤ target, since values decrease there. If it holdstarget, return it; otherwise return −1.
Three searches of O(log n) reads each. The order of steps 2 and 3 is what makes the answer the smallest index.
Writing one boundary-search function that takes a range and a test, and calling it three times, is shorter and less error-prone than three hand-written loops. Interviewers notice the reuse.
Complexity. Time O(log n) · Space O(1)
The solution, in six languages
Each one is compiled and run against the test table before it is published. Java is reviewed by hand.
def find_in_mountain(a, target):
def first(lo, hi, test): # first i in [lo, hi] with test(i), or hi if none before it
while lo < hi:
mid = (lo + hi) // 2
if test(mid):
hi = mid
else:
lo = mid + 1
return lo
n = len(a)
peak = first(0, n - 1, lambda i: a[i] > a[i + 1])
i = first(0, peak, lambda i: a[i] >= target) # ascending side
if a[i] == target:
return i
j = first(peak + 1, n - 1, lambda i: a[i] <= target) # descending side
return j if a[j] == target else -1#include <vector>
#include <functional>
// first i in [lo, hi] with test(i), or hi if none before it
static int first(int lo, int hi, const std::function<bool(int)>& test) {
while (lo < hi) {
int mid = lo + (hi - lo) / 2;
if (test(mid)) hi = mid; else lo = mid + 1;
}
return lo;
}
int solve(const std::vector<int>& a, int target) {
const int n = (int)a.size();
int peak = first(0, n - 1, [&](int i) { return a[i] > a[i + 1]; });
int i = first(0, peak, [&](int k) { return a[k] >= target; }); // ascending side
if (a[i] == target) return i;
int j = first(peak + 1, n - 1, [&](int k) { return a[k] <= target; }); // descending side
return a[j] == target ? j : -1;
}import java.util.function.IntPredicate;
class Solution {
// first i in [lo, hi] with test(i), or hi if none before it
private int first(int lo, int hi, IntPredicate test) {
while (lo < hi) {
int mid = lo + (hi - lo) / 2;
if (test.test(mid)) hi = mid; else lo = mid + 1;
}
return lo;
}
public int findInMountain(int[] a, int target) {
int n = a.length;
int peak = first(0, n - 1, i -> a[i] > a[i + 1]);
int i = first(0, peak, k -> a[k] >= target); // ascending side
if (a[i] == target) return i;
int j = first(peak + 1, n - 1, k -> a[k] <= target); // descending side
return a[j] == target ? j : -1;
}
}function findInMountain(a, target) {
// first i in [lo, hi] with test(i), or hi if none before it
const first = (lo, hi, test) => {
while (lo < hi) {
const mid = (lo + hi) >> 1;
if (test(mid)) hi = mid; else lo = mid + 1;
}
return lo;
};
const n = a.length;
const peak = first(0, n - 1, i => a[i] > a[i + 1]);
const i = first(0, peak, k => a[k] >= target); // ascending side
if (a[i] === target) return i;
const j = first(peak + 1, n - 1, k => a[k] <= target); // descending side
return a[j] === target ? j : -1;
}// first i in [lo, hi] with test(i), or hi if none before it
fn first(mut lo: usize, mut hi: usize, test: impl Fn(usize) -> bool) -> usize {
while lo < hi {
let mid = lo + (hi - lo) / 2;
if test(mid) { hi = mid } else { lo = mid + 1 }
}
lo
}
fn solve(a: &[i64], target: i64) -> i64 {
let n = a.len();
let peak = first(0, n - 1, |i| a[i] > a[i + 1]);
let i = first(0, peak, |k| a[k] >= target); // ascending side
if a[i] == target {
return i as i64;
}
let j = first(peak + 1, n - 1, |k| a[k] <= target); // descending side
if a[j] == target { j as i64 } else { -1 }
}import Data.Array
-- first i in [lo, hi] with test i, or hi if none before it
firstIn :: Int -> Int -> (Int -> Bool) -> Int
firstIn lo hi test
| lo >= hi = lo
| test mid = firstIn lo mid test
| otherwise = firstIn (mid + 1) hi test
where mid = lo + (hi - lo) `div` 2
solve :: [Int] -> Int -> Int
solve xs target
| a ! i == target = i
| a ! j == target = j
| otherwise = -1
where
n = length xs
a = listArray (0, n - 1) xs
peak = firstIn 0 (n - 1) (\k -> a ! k > a ! (k + 1))
i = firstIn 0 peak (\k -> a ! k >= target) -- ascending side
j = firstIn (peak + 1) (n - 1) (\k -> a ! k <= target) -- descending side
Variants to try
- The array is bitonic but may start by decreasing: find the minimum instead of the peak, with the same three-search shape.
- Count how many times the target appears: run both side searches and add.
Next problems
The top of a mountain array
Nothing is sorted, but "is the next element smaller?" is false up the slope and true down it. Find the first true.
Open the problem → Medium15 minThe one value without a partner
Before the single value, pairs start at even indices; after it, at odd ones. That switch is what the search finds.
Open the problem → Easy10 minFind a value in a sorted array
The search every other binary search is built on: halve a closed range until the value is found or the range is empty.
Open the problem →Write a solution and run it against the real test table.
Keep going
The top of a mountain array
Nothing is sorted, but "is the next element smaller?" is false up the slope and true down it. Find the first true.
Open the problem → Medium15 minThe one value without a partner
Before the single value, pairs start at even indices; after it, at odd ones. That switch is what the search finds.
Open the problem → Easy10 minFind a value in a sorted array
The search every other binary search is built on: halve a closed range until the value is found or the range is empty.
Open the problem → Binary searchThe pattern behind it
Search the answer, not the array: halve a monotone predicate until one candidate is left.
Read the pattern →Preparing for a real process? Quant interview preparation, or book a free 20-minute call.