845. Longest Mountain in Array
Problem Description
A mountain array is defined as an array that meets the following conditions:
- The array has at least 3 elements (
arr.length >= 3
) - There exists a peak index
i
where0 < i < arr.length - 1
such that:- Elements before the peak are in strictly increasing order:
arr[0] < arr[1] < ... < arr[i-1] < arr[i]
- Elements after the peak are in strictly decreasing order:
arr[i] > arr[i+1] > ... > arr[arr.length-1]
- Elements before the peak are in strictly increasing order:
Given an integer array arr
, you need to find the length of the longest contiguous subarray that forms a mountain. If no such mountain subarray exists, return 0
.
The solution uses dynamic programming with two arrays:
- Array
f[i]
tracks the length of the longest increasing sequence ending at positioni
- Array
g[i]
tracks the length of the longest decreasing sequence starting at positioni
The algorithm works in two passes:
- Forward pass: Build array
f
by checking if each element is greater than the previous one. Ifarr[i] > arr[i-1]
, thenf[i] = f[i-1] + 1
- Backward pass: Build array
g
by checking if each element is greater than the next one. Ifarr[i] > arr[i+1]
, theng[i] = g[i+1] + 1
For each position i
that can be a peak (where both f[i] > 1
and g[i] > 1
), the mountain length is calculated as f[i] + g[i] - 1
(subtracting 1 because the peak is counted in both arrays). The maximum of all valid mountain lengths is returned as the answer.
Intuition
The key insight is that a mountain has three parts: an ascending slope, a peak, and a descending slope. Instead of checking every possible subarray to see if it forms a mountain (which would be inefficient), we can think about each position as a potential peak and calculate how far we can extend in both directions.
For any position to be a valid peak, we need to know:
- How many consecutive increasing elements lead up to it (including itself)
- How many consecutive decreasing elements follow from it (including itself)
This naturally leads to a dynamic programming approach where we precompute this information for every position. If we know the "uphill distance" and "downhill distance" for each point, we can determine if it's a valid peak and calculate the mountain length.
Consider the array [2, 1, 4, 7, 3, 2, 5]
. At position 3 (value 7):
- Looking left: we have an increasing sequence
1 → 4 → 7
of length 3 - Looking right: we have a decreasing sequence
7 → 3 → 2
of length 3 - This forms a mountain of length
3 + 3 - 1 = 5
(we subtract 1 because 7 is counted twice)
The beauty of this approach is that we can compute all increasing sequences in one forward pass and all decreasing sequences in one backward pass. Then, for each position that has both an increasing sequence before it (f[i] > 1
) and a decreasing sequence after it (g[i] > 1
), we know it can be a peak of a mountain.
By checking during the backward pass (when we're already computing g[i]
), we can simultaneously identify valid peaks and calculate mountain lengths, making the solution efficient with O(n)
time complexity.
Learn more about Two Pointers and Dynamic Programming patterns.
Solution Approach
The implementation uses two auxiliary arrays and two passes through the input array to efficiently find the longest mountain:
Data Structures:
f[i]
: Stores the length of the longest increasing sequence ending at indexi
g[i]
: Stores the length of the longest decreasing sequence starting at indexi
- Both arrays are initialized with 1s since each element by itself has a sequence length of 1
Algorithm Steps:
-
Build the increasing sequence array (
f
):for i in range(1, n): if arr[i] > arr[i - 1]: f[i] = f[i - 1] + 1
- Iterate forward through the array starting from index 1
- If current element is greater than the previous one, extend the increasing sequence:
f[i] = f[i-1] + 1
- Otherwise,
f[i]
remains 1 (starts a new sequence)
-
Build the decreasing sequence array (
g
) and find the maximum mountain:for i in range(n - 2, -1, -1): if arr[i] > arr[i + 1]: g[i] = g[i + 1] + 1 if f[i] > 1: ans = max(ans, f[i] + g[i] - 1)
- Iterate backward through the array starting from index
n-2
- If current element is greater than the next one, extend the decreasing sequence:
g[i] = g[i+1] + 1
- Check if position
i
can be a peak: it needsf[i] > 1
(has an increasing part) andg[i] > 1
(implicitly true since we just set it) - Calculate mountain length at this peak:
f[i] + g[i] - 1
- Update the maximum mountain length found so far
- Iterate backward through the array starting from index
Why this works:
- A valid mountain peak at position
i
must have both:- An increasing sequence leading to it:
f[i] > 1
- A decreasing sequence starting from it:
g[i] > 1
- An increasing sequence leading to it:
- The mountain length is
f[i] + g[i] - 1
because positioni
is counted in both arrays - By checking these conditions during the backward pass, we avoid a third loop and keep the time complexity at
O(n)
Time Complexity: O(n)
- two passes through the array
Space Complexity: O(n)
- for the two auxiliary arrays f
and g
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 the algorithm with the array [2, 1, 4, 7, 3, 2, 5]
.
Initial Setup:
- Input array:
[2, 1, 4, 7, 3, 2, 5]
- Initialize
f = [1, 1, 1, 1, 1, 1, 1]
(length of increasing sequence ending at each position) - Initialize
g = [1, 1, 1, 1, 1, 1, 1]
(length of decreasing sequence starting at each position) - Initialize
ans = 0
(maximum mountain length)
Step 1: Forward Pass (Build array f
)
Starting from index 1, check if each element is greater than the previous:
- i=1:
arr[1]=1
vsarr[0]=2
→ 1 < 2, sof[1]
stays 1 - i=2:
arr[2]=4
vsarr[1]=1
→ 4 > 1, sof[2] = f[1] + 1 = 2
- i=3:
arr[3]=7
vsarr[2]=4
→ 7 > 4, sof[3] = f[2] + 1 = 3
- i=4:
arr[4]=3
vsarr[3]=7
→ 3 < 7, sof[4]
stays 1 - i=5:
arr[5]=2
vsarr[4]=3
→ 2 < 3, sof[5]
stays 1 - i=6:
arr[6]=5
vsarr[5]=2
→ 5 > 2, sof[6] = f[5] + 1 = 2
After forward pass: f = [1, 1, 2, 3, 1, 1, 2]
Step 2: Backward Pass (Build array g
and find maximum mountain)
Starting from index 5 (n-2), check if each element is greater than the next:
-
i=5:
arr[5]=2
vsarr[6]=5
→ 2 < 5, sog[5]
stays 1- Check peak:
f[5]=1
(not > 1), skip
- Check peak:
-
i=4:
arr[4]=3
vsarr[5]=2
→ 3 > 2, sog[4] = g[5] + 1 = 2
- Check peak:
f[4]=1
(not > 1), skip
- Check peak:
-
i=3:
arr[3]=7
vsarr[4]=3
→ 7 > 3, sog[3] = g[4] + 1 = 3
- Check peak:
f[3]=3 > 1
✓ andg[3]=3 > 1
✓ - Mountain length =
f[3] + g[3] - 1 = 3 + 3 - 1 = 5
- Update
ans = 5
- Check peak:
-
i=2:
arr[2]=4
vsarr[3]=7
→ 4 < 7, sog[2]
stays 1- Check peak:
f[2]=2 > 1
butg[2]=1
(not > 1), skip
- Check peak:
-
i=1:
arr[1]=1
vsarr[2]=4
→ 1 < 4, sog[1]
stays 1- Check peak:
f[1]=1
(not > 1), skip
- Check peak:
-
i=0:
arr[0]=2
vsarr[1]=1
→ 2 > 1, sog[0] = g[1] + 1 = 2
- Check peak:
f[0]=1
(not > 1), skip
- Check peak:
After backward pass: g = [2, 1, 1, 3, 2, 1, 1]
Final Result:
- The maximum mountain length found is
5
- This corresponds to the subarray
[1, 4, 7, 3, 2]
with peak at index 3 (value 7) - The mountain has:
- Increasing part:
1 → 4 → 7
(length 3) - Decreasing part:
7 → 3 → 2
(length 3) - Total length: 5 elements (counting the peak once)
- Increasing part:
Solution Implementation
1class Solution:
2 def longestMountain(self, arr: List[int]) -> int:
3 n = len(arr)
4
5 # left[i] stores the length of increasing sequence ending at index i
6 left = [1] * n
7
8 # right[i] stores the length of decreasing sequence starting at index i
9 right = [1] * n
10
11 # Build left array: count consecutive increasing elements from left
12 for i in range(1, n):
13 if arr[i] > arr[i - 1]:
14 left[i] = left[i - 1] + 1
15
16 max_length = 0
17
18 # Build right array: count consecutive decreasing elements from right
19 # and calculate the mountain length at the same time
20 for i in range(n - 2, -1, -1):
21 if arr[i] > arr[i + 1]:
22 right[i] = right[i + 1] + 1
23
24 # Valid mountain requires uphill (left[i] > 1) and downhill (right[i] > 1)
25 if left[i] > 1:
26 # Mountain length = uphill + downhill - 1 (peak counted twice)
27 max_length = max(max_length, left[i] + right[i] - 1)
28
29 return max_length
30
1class Solution {
2 public int longestMountain(int[] arr) {
3 int n = arr.length;
4
5 // leftIncreasing[i] stores the length of increasing sequence ending at index i
6 int[] leftIncreasing = new int[n];
7 // rightDecreasing[i] stores the length of decreasing sequence starting at index i
8 int[] rightDecreasing = new int[n];
9
10 // Initialize both arrays with 1 (each element forms a sequence of length 1)
11 Arrays.fill(leftIncreasing, 1);
12 Arrays.fill(rightDecreasing, 1);
13
14 // Calculate the length of increasing sequences from left to right
15 // If current element is greater than previous, extend the increasing sequence
16 for (int i = 1; i < n; ++i) {
17 if (arr[i] > arr[i - 1]) {
18 leftIncreasing[i] = leftIncreasing[i - 1] + 1;
19 }
20 }
21
22 int maxMountainLength = 0;
23
24 // Calculate the length of decreasing sequences from right to left
25 // Also calculate the mountain length when we have both increasing and decreasing parts
26 for (int i = n - 2; i >= 0; --i) {
27 if (arr[i] > arr[i + 1]) {
28 // Extend the decreasing sequence
29 rightDecreasing[i] = rightDecreasing[i + 1] + 1;
30
31 // Check if current position can be a peak (has increasing part on left)
32 if (leftIncreasing[i] > 1) {
33 // Calculate mountain length: increasing + decreasing - 1 (peak counted twice)
34 int currentMountainLength = leftIncreasing[i] + rightDecreasing[i] - 1;
35 maxMountainLength = Math.max(maxMountainLength, currentMountainLength);
36 }
37 }
38 }
39
40 return maxMountainLength;
41 }
42}
43
1class Solution {
2public:
3 int longestMountain(vector<int>& arr) {
4 int n = arr.size();
5
6 // Arrays to store the length of increasing/decreasing sequences
7 // increasing[i]: length of strictly increasing sequence ending at i
8 // decreasing[i]: length of strictly decreasing sequence starting at i
9 vector<int> increasing(n, 1);
10 vector<int> decreasing(n, 1);
11
12 // Calculate the length of increasing sequences from left to right
13 // If current element is greater than previous, extend the sequence
14 for (int i = 1; i < n; ++i) {
15 if (arr[i] > arr[i - 1]) {
16 increasing[i] = increasing[i - 1] + 1;
17 }
18 }
19
20 int maxMountainLength = 0;
21
22 // Calculate the length of decreasing sequences from right to left
23 // Also check if current position can be a peak of a mountain
24 for (int i = n - 2; i >= 0; --i) {
25 if (arr[i] > arr[i + 1]) {
26 decreasing[i] = decreasing[i + 1] + 1;
27
28 // A valid mountain peak must have increasing sequence before it
29 // (increasing[i] > 1) ensures there's at least one element before peak
30 if (increasing[i] > 1) {
31 // Mountain length = left side + right side - 1 (peak counted twice)
32 int currentMountainLength = increasing[i] + decreasing[i] - 1;
33 maxMountainLength = max(maxMountainLength, currentMountainLength);
34 }
35 }
36 }
37
38 return maxMountainLength;
39 }
40};
41
1function longestMountain(arr: number[]): number {
2 const n: number = arr.length;
3
4 // Arrays to store the length of increasing/decreasing sequences
5 // increasing[i]: length of strictly increasing sequence ending at i
6 // decreasing[i]: length of strictly decreasing sequence starting at i
7 const increasing: number[] = new Array(n).fill(1);
8 const decreasing: number[] = new Array(n).fill(1);
9
10 // Calculate the length of increasing sequences from left to right
11 // If current element is greater than previous, extend the sequence
12 for (let i = 1; i < n; i++) {
13 if (arr[i] > arr[i - 1]) {
14 increasing[i] = increasing[i - 1] + 1;
15 }
16 }
17
18 let maxMountainLength: number = 0;
19
20 // Calculate the length of decreasing sequences from right to left
21 // Also check if current position can be a peak of a mountain
22 for (let i = n - 2; i >= 0; i--) {
23 if (arr[i] > arr[i + 1]) {
24 decreasing[i] = decreasing[i + 1] + 1;
25
26 // A valid mountain peak must have increasing sequence before it
27 // (increasing[i] > 1) ensures there's at least one element before peak
28 if (increasing[i] > 1) {
29 // Mountain length = left side + right side - 1 (peak counted twice)
30 const currentMountainLength: number = increasing[i] + decreasing[i] - 1;
31 maxMountainLength = Math.max(maxMountainLength, currentMountainLength);
32 }
33 }
34 }
35
36 return maxMountainLength;
37}
38
Time and Space Complexity
Time Complexity: O(n)
where n
is the length of the input array.
The algorithm consists of three main parts:
- First loop: Iterates through the array once from index 1 to n-1 to populate array
f
, takingO(n)
time - Second loop: Iterates through the array once from index n-2 to 0 to populate array
g
and compute the maximum mountain length, takingO(n)
time - Array initialization: Creating two arrays of size n takes
O(n)
time
Total time complexity: O(n) + O(n) + O(n) = O(n)
Space Complexity: O(n)
where n
is the length of the input array.
The algorithm uses:
- Array
f
of sizen
to store the length of increasing sequences ending at each position:O(n)
space - Array
g
of sizen
to store the length of decreasing sequences starting at each position:O(n)
space - Variable
ans
and other constant variables:O(1)
space
Total space complexity: O(n) + O(n) + O(1) = O(n)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Forgetting to Check Valid Peak Conditions
Pitfall: A common mistake is calculating the mountain length at every position without verifying that it's actually a valid peak. A valid peak must have both an increasing sequence before it AND a decreasing sequence after it.
Incorrect Implementation:
# WRONG: This calculates mountain length even for non-peaks
for i in range(n):
max_length = max(max_length, left[i] + right[i] - 1)
Why it's wrong: This would incorrectly count sequences that are only increasing (like [1,2,3]) or only decreasing (like [3,2,1]) as mountains, when they shouldn't be.
Correct Implementation:
# Only calculate when we have both uphill and downhill
if left[i] > 1 and right[i] > 1:
max_length = max(max_length, left[i] + right[i] - 1)
2. Off-by-One Error in Loop Boundaries
Pitfall: Using incorrect loop boundaries when building the right
array, especially starting from n-1
instead of n-2
.
Incorrect Implementation:
# WRONG: Starting from n-1 causes index out of bounds
for i in range(n - 1, -1, -1):
if arr[i] > arr[i + 1]: # Error: arr[n] doesn't exist
right[i] = right[i + 1] + 1
Correct Implementation:
# Start from n-2 since we need to compare with i+1
for i in range(n - 2, -1, -1):
if arr[i] > arr[i + 1]:
right[i] = right[i + 1] + 1
3. Not Handling Edge Cases Properly
Pitfall: Not considering arrays that are too small to form mountains or arrays with duplicate values.
Example problematic cases:
arr = [1, 2, 2, 3, 1]
- Contains duplicates, which breaks the "strictly" increasing/decreasing requirementarr = [1, 2]
- Too short to form a mountain
Solution: The algorithm naturally handles these cases:
- Duplicates: The condition
arr[i] > arr[i-1]
(not>=
) ensures duplicates break the sequence - Short arrays: Mountains require at least 3 elements, and our peak validation (
left[i] > 1
andright[i] > 1
) ensures at least 3 elements total
4. Checking Peak Validity in the Wrong Pass
Pitfall: Attempting to check for valid mountains during the forward pass when the right
array hasn't been built yet.
Incorrect Implementation:
# WRONG: right array isn't built yet during forward pass
for i in range(1, n):
if arr[i] > arr[i - 1]:
left[i] = left[i - 1] + 1
if left[i] > 1 and right[i] > 1: # right[i] is still 1!
max_length = max(max_length, left[i] + right[i] - 1)
Correct Implementation:
# Check for mountains during backward pass when both arrays are ready
for i in range(n - 2, -1, -1):
if arr[i] > arr[i + 1]:
right[i] = right[i + 1] + 1
if left[i] > 1: # Now both left[i] and right[i] are valid
max_length = max(max_length, left[i] + right[i] - 1)
What's the relationship between a tree and a graph?
Recommended Readings
Tech Interview Pattern Two Pointers Introduction If you prefer videos here's a super quick introduction to Two Pointers div class responsive iframe iframe src https www youtube com embed xZ4AfXHQ1VQ title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture allowfullscreen iframe div Two pointers is a common interview
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
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
Want a Structured Path to Master System Design Too? Don’t Miss This!