Longest Mountain in Array: Expand From Peaks
LeetCode's Longest Mountain in Array (problem 845) asks you to find the longest contiguous subarray that forms a mountain shape. It is a medium-level problem, but the optimal approach is clean and teaches a pattern that shows up in many array problems: find a center point, then expand outward.
The problem
You are given an integer array arr. A mountain is a subarray where values strictly increase to a peak and then strictly decrease. The mountain must have length 3 or more (at least one element going up, the peak, and at least one element going down).
Return the length of the longest mountain. If there is no mountain, return 0.
The key rules:
- The ascent must be strictly increasing (no equal values)
- The descent must be strictly decreasing (no equal values)
- The mountain needs at least 3 elements: one upslope, the peak, and one downslope
- A plateau (like
[3, 3, 3]) does not count
Approach 1: Expand from peaks (O(n) time, O(1) space)
The most natural way to think about this: find every peak, then expand left and right to measure the mountain.
A peak is any index i where arr[i - 1] is less than arr[i] and arr[i] is greater than arr[i + 1]. Once you find a peak, expand left while values keep decreasing (going backward) and expand right while values keep decreasing (going forward). The mountain length is right - left + 1.
The trick that makes this O(n) instead of O(n^2): after measuring a mountain, jump i forward to the right boundary. Every element in the mountain has already been accounted for.
def longestMountain(arr: list[int]) -> int:
n = len(arr)
result = 0
i = 1
while i < n - 1:
if arr[i - 1] < arr[i] and arr[i] > arr[i + 1]:
left = i - 1
while left > 0 and arr[left - 1] < arr[left]:
left -= 1
right = i + 1
while right < n - 1 and arr[right] > arr[right + 1]:
right += 1
result = max(result, right - left + 1)
i = right
else:
i += 1
return result
The jump i = right after finding a mountain is what keeps this linear. Without it, you would re-scan elements that are already part of a known mountain. This is the same optimization used in problems like finding all palindromic substrings: expand from center, then skip what you have already covered.
Approach 2: DP with up/down arrays (O(n) time, O(n) space)
An alternative approach uses two auxiliary arrays:
up[i]: the number of consecutive increasing steps ending at indexidown[i]: the number of consecutive decreasing steps starting at indexi
If both up[i] and down[i] are greater than 0, then index i is a peak and the mountain length is up[i] + down[i] + 1.
def longestMountain(arr: list[int]) -> int:
n = len(arr)
if n < 3:
return 0
up = [0] * n
down = [0] * n
for i in range(1, n):
if arr[i] > arr[i - 1]:
up[i] = up[i - 1] + 1
for i in range(n - 2, -1, -1):
if arr[i] > arr[i + 1]:
down[i] = down[i + 1] + 1
result = 0
for i in range(n):
if up[i] > 0 and down[i] > 0:
result = max(result, up[i] + down[i] + 1)
return result
This is conceptually simpler. The forward pass builds up, the backward pass builds down, and a final scan checks every potential peak. The trade-off is O(n) extra space for the two arrays.
The DP approach is the same forward-backward pattern used in Candy, Best Time to Buy and Sell Stock III, and Product of Array Except Self. If you have drilled that pattern, this solution writes itself.
Expand-from-peak walkthrough
Let's trace the expand-from-peak approach on arr = [2, 1, 4, 7, 3, 2, 5].
Step 1: Scan for peaks. A peak is arr[i] greater than both arr[i-1] and arr[i+1].
Walk through indices 1 to n-2. Check whether each element is strictly greater than both neighbors.
Step 2: Found peak at index 3 (value 7). Start expanding left.
arr[3] = 7 is greater than arr[2] = 4 and arr[4] = 3. Expand left: arr[1] = 1 is less than arr[2] = 4, so left moves to index 1.
Step 3: Expand left further, then expand right from peak.
Left stops at index 1 (arr[0] = 2 is not less than arr[1] = 1). Right expands: arr[5] = 2 is less than arr[4] = 3, then arr[6] = 5 is not less than arr[5] = 2, so right stops at 5.
Step 4: Mountain length = right - left + 1 = 5 - 1 + 1 = 5.
The mountain spans indices 1 through 5: [1, 4, 7, 3, 2]. Update result = max(0, 5) = 5.
Step 5: Jump i to right pointer (index 5). Continue scanning.
Set i = right = 5. We skip indices we already processed. Check index 5: arr[4] = 3 is greater than arr[5] = 2, but arr[5] = 2 is less than arr[6] = 5. Not a peak.
Step 6: No more valid peaks. Return 5.
Index 6 is the last element and cannot be a peak. The longest mountain has length 5.
Notice how after finding the mountain at index 3, we jump i to the right boundary at index 5. This avoids re-examining indices 2, 3, and 4. Every element is visited at most twice (once during the scan, once during expansion), so the total work is O(n).
Complexity summary
| Approach | Time | Space |
|---|---|---|
| Expand from peaks | O(n) | O(1) |
| DP with up/down arrays | O(n) | O(n) |
Both are linear in time. The expand-from-peaks approach wins on space, and it is what most interviewers expect for this problem.
The building blocks
This problem is built from two patterns that recur across many LeetCode problems:
Peak detection
Scanning for elements greater than both neighbors is a local extremum check. The same idea appears in:
- Find Peak Element (binary search variant on local peaks)
- Wiggle Sort (ensuring alternating peaks and valleys)
- Trapping Rain Water (identifying boundaries for water containment)
Whenever a problem involves "shape" within an array, peak detection is usually the starting point.
Expand from center
Once you locate a center point (a peak, a character in a palindrome, a pivot), you expand outward in both directions. This is the same pattern behind:
- Longest Palindromic Substring (expand from each character or pair)
- Largest Rectangle in Histogram (expand from each bar)
- Container With Most Water (two-pointer convergence from the edges)
The key insight is always the same: the center anchors the search, and expansion stops when a condition breaks.
Edge cases
- No mountain exists:
arr = [1, 2, 3](only ascending, no descent). Return 0. - Plateau:
arr = [2, 2, 2]. Equal elements do not count as increasing or decreasing. Return 0. - All descending:
arr = [5, 4, 3, 2]. No peak exists. Return 0. - Mountain of exactly length 3:
arr = [1, 3, 2]. One up step, peak, one down step. Return 3. - Array too short: If
len(arr)is less than 3, no mountain is possible. Return 0. - Multiple mountains: The algorithm checks every peak and keeps the maximum.
From understanding to recall
You have seen both approaches and traced through the example. But reading a solution once does not mean you can reproduce it under pressure. The expand-from-peak approach has two small details that are easy to forget: the while loop conditions for left and right expansion, and the jump i = right that keeps the runtime linear. These are the kind of details that slip away without practice.
The building blocks here (peak detection and expand from center) are small enough to drill individually. Once you can write both from memory, combining them into the full solution becomes natural.
Related posts
- Best Time to Buy and Sell Stock - Another array problem where forward-backward thinking helps identify the optimal structure
- Container With Most Water - Two-pointer technique on array shapes, similar expand-from-boundary reasoning
- Trapping Rain Water - The classic array shape problem that combines peak detection with forward-backward passes