941. Valid Mountain Array
Problem Description
You are given an array of integers arr
. Your task is to determine if this array represents a valid mountain array.
A mountain array must satisfy the following conditions:
-
The array must have at least 3 elements (
arr.length >= 3
) -
There must exist some index
i
where0 < i < arr.length - 1
such that:- The elements strictly increase from the start up to index
i
:arr[0] < arr[1] < ... < arr[i - 1] < arr[i]
- The elements strictly decrease from index
i
to the end:arr[i] > arr[i + 1] > ... > arr[arr.length - 1]
- The elements strictly increase from the start up to index
In other words, the array should first strictly increase to reach a peak, then strictly decrease from that peak. The peak cannot be at the first or last position of the array.
Return true
if the array is a valid mountain array, otherwise return false
.
For example:
[0, 3, 2, 1]
is a valid mountain array (increases to 3, then decreases)[3, 5, 5]
is not valid (contains equal consecutive elements)[0, 1, 2, 3]
is not valid (only increases, never decreases)[3, 2, 1]
is not valid (only decreases, never increases)
Intuition
The key insight is that a valid mountain array has exactly one peak - a point where the array transitions from strictly increasing to strictly decreasing. We can think of this peak as the "summit" of the mountain.
Instead of checking every element sequentially, we can approach this problem from both ends simultaneously. Imagine two hikers: one starting from the left side of the mountain and climbing up, and another starting from the right side and also climbing up (which means going backwards through the array).
The left hiker will keep climbing as long as each next step is higher than the current one (arr[i] < arr[i + 1]
). They'll stop when they can't go higher anymore - this should be at the peak.
Similarly, the right hiker moves leftward as long as each previous element is higher than the current one (arr[j - 1] > arr[j]
). They'll also stop when they can't climb higher - which should also be at the peak.
If both hikers meet at the same position (i == j
), and neither got stuck at the very beginning or end of the array, then we've found a valid mountain with a single peak. If they stop at different positions, it means either:
- There's a plateau (flat section) somewhere
- The array doesn't strictly increase then decrease
- There are multiple peaks
This two-pointer approach elegantly verifies both the ascending and descending requirements while also ensuring there's exactly one peak in the valid range.
Solution Approach
The implementation uses a two-pointer technique to efficiently validate the mountain array:
Step 1: Initial Length Check
if n < 3: return False
First, we check if the array has at least 3 elements. If not, it cannot form a mountain, so we immediately return false
.
Step 2: Find the Peak from the Left
i = 0 while i + 1 < n - 1 and arr[i] < arr[i + 1]: i += 1
We initialize pointer i
at position 0 and move it rightward as long as the array is strictly increasing (arr[i] < arr[i + 1]
). The condition i + 1 < n - 1
ensures that the peak cannot be at the last position. When this loop ends, i
should be at the peak position if the array is a valid mountain.
Step 3: Find the Peak from the Right
j = n - 1 while j - 1 > 0 and arr[j - 1] > arr[j]: j -= 1
We initialize pointer j
at the last position and move it leftward as long as the array is strictly decreasing when viewed from right to left (arr[j - 1] > arr[j]
). The condition j - 1 > 0
ensures that the peak cannot be at the first position. When this loop ends, j
should also be at the peak position if the array is valid.
Step 4: Validate the Mountain
return i == j
If both pointers meet at the same position (i == j
), it confirms:
- There's a strictly increasing sequence from the start to position
i
- There's a strictly decreasing sequence from position
j
to the end - There's exactly one peak in the array
- The peak is neither at the first nor the last position
If i != j
, it means the array either has a plateau, multiple peaks, or doesn't follow the strict increase-then-decrease pattern required for a mountain array.
Time Complexity: O(n)
where n
is the length of the array, as we traverse the array at most once from each end.
Space Complexity: O(1)
as we only use two pointers regardless of input size.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with the array [1, 3, 5, 4, 2]
:
Step 1: Initial Setup
- Array:
[1, 3, 5, 4, 2]
, lengthn = 5
- Since
n >= 3
, we proceed - Initialize
i = 0
(left pointer) andj = 4
(right pointer)
Step 2: Left Pointer Climbing Up
- Start at
i = 0
: Check ifarr[0] < arr[1]
→1 < 3
✓, movei
to 1 - At
i = 1
: Check ifarr[1] < arr[2]
→3 < 5
✓, movei
to 2 - At
i = 2
: Check ifarr[2] < arr[3]
→5 < 4
✗, stop - Left pointer stops at
i = 2
(the peak)
Step 3: Right Pointer Climbing Up (from right)
- Start at
j = 4
: Check ifarr[3] > arr[4]
→4 > 2
✓, movej
to 3 - At
j = 3
: Check ifarr[2] > arr[3]
→5 > 4
✓, movej
to 2 - At
j = 2
: Check ifarr[1] > arr[2]
→3 > 5
✗, stop - Right pointer stops at
j = 2
(the peak)
Step 4: Validation
- Both pointers meet at position 2:
i == j == 2
✓ - This confirms element at index 2 (value 5) is the single peak
- The array strictly increases
[1, 3, 5]
then strictly decreases[5, 4, 2]
- Return
true
- this is a valid mountain array!
Counter-Example: [1, 2, 2, 3]
- Left pointer: Moves from 0 to 1 (
1 < 2
✓), stops at 1 (2 < 2
✗) - Right pointer: Moves from 3 to 2 (
2 > 3
✗), stays at 3 - Pointers don't meet:
i = 1
,j = 3
- Return
false
- not a valid mountain (has equal consecutive elements)
Solution Implementation
1class Solution:
2 def validMountainArray(self, arr: List[int]) -> bool:
3 """
4 Determines if the given array is a valid mountain array.
5 A valid mountain array must:
6 1. Have at least 3 elements
7 2. Strictly increase to a peak
8 3. Strictly decrease after the peak
9
10 Args:
11 arr: List of integers to check
12
13 Returns:
14 bool: True if arr is a valid mountain array, False otherwise
15 """
16 n = len(arr)
17
18 # Mountain array must have at least 3 elements
19 if n < 3:
20 return False
21
22 # Use two pointers approach
23 left_pointer = 0
24 right_pointer = n - 1
25
26 # Climb up from the left side
27 # Stop when we reach peak or can't go further
28 while left_pointer + 1 < n - 1 and arr[left_pointer] < arr[left_pointer + 1]:
29 left_pointer += 1
30
31 # Climb up from the right side (going backwards)
32 # Stop when we reach peak or can't go further
33 while right_pointer - 1 > 0 and arr[right_pointer - 1] > arr[right_pointer]:
34 right_pointer -= 1
35
36 # For a valid mountain, both pointers should meet at the same peak
37 return left_pointer == right_pointer
38
1class Solution {
2 public boolean validMountainArray(int[] arr) {
3 // Get the length of the array
4 int arrayLength = arr.length;
5
6 // A valid mountain array must have at least 3 elements
7 if (arrayLength < 3) {
8 return false;
9 }
10
11 // Initialize two pointers: left pointer starts from beginning
12 int leftPointer = 0;
13 // Right pointer starts from end
14 int rightPointer = arrayLength - 1;
15
16 // Climb up from the left side
17 // Move left pointer forward while the array is strictly increasing
18 // Stop before reaching the last element to ensure there's a descending part
19 while (leftPointer + 1 < arrayLength - 1 && arr[leftPointer] < arr[leftPointer + 1]) {
20 leftPointer++;
21 }
22
23 // Climb up from the right side (going backwards)
24 // Move right pointer backward while the array is strictly decreasing (from right to left)
25 // Stop after the first element to ensure there's an ascending part
26 while (rightPointer - 1 > 0 && arr[rightPointer - 1] > arr[rightPointer]) {
27 rightPointer--;
28 }
29
30 // For a valid mountain array, both pointers should meet at the same peak
31 return leftPointer == rightPointer;
32 }
33}
34
1class Solution {
2public:
3 bool validMountainArray(vector<int>& arr) {
4 int n = arr.size();
5
6 // Mountain array must have at least 3 elements
7 if (n < 3) {
8 return false;
9 }
10
11 // Use two pointers to find the peak
12 int left = 0;
13 int right = n - 1;
14
15 // Move left pointer up the ascending slope
16 // Stop when we reach peak or second-to-last element
17 while (left + 1 < n - 1 && arr[left] < arr[left + 1]) {
18 ++left;
19 }
20
21 // Move right pointer up the descending slope (from right to left)
22 // Stop when we reach peak or second element
23 while (right - 1 > 0 && arr[right - 1] > arr[right]) {
24 --right;
25 }
26
27 // Valid mountain: both pointers meet at the same peak position
28 // This ensures there's both an ascending and descending portion
29 return left == right;
30 }
31};
32
1/**
2 * Validates if an array represents a valid mountain array.
3 * A valid mountain array must:
4 * 1. Have at least 3 elements
5 * 2. Strictly increase to a peak
6 * 3. Strictly decrease after the peak
7 * 4. Peak cannot be at the first or last position
8 *
9 * @param arr - The input array to validate
10 * @returns true if the array is a valid mountain array, false otherwise
11 */
12function validMountainArray(arr: number[]): boolean {
13 const arrayLength: number = arr.length;
14
15 // Mountain array must have at least 3 elements
16 if (arrayLength < 3) {
17 return false;
18 }
19
20 // Initialize two pointers: left starting from beginning, right from end
21 let leftPointer: number = 0;
22 let rightPointer: number = arrayLength - 1;
23
24 // Move left pointer forward while values are strictly increasing
25 // Stop before reaching the last element to ensure peak is not at the end
26 while (leftPointer + 1 < arrayLength - 1 && arr[leftPointer] < arr[leftPointer + 1]) {
27 leftPointer++;
28 }
29
30 // Move right pointer backward while values are strictly decreasing (from right to left)
31 // Stop before reaching the first element to ensure peak is not at the beginning
32 while (rightPointer - 1 > 0 && arr[rightPointer] < arr[rightPointer - 1]) {
33 rightPointer--;
34 }
35
36 // Both pointers should meet at the same peak position for a valid mountain
37 return leftPointer === rightPointer;
38}
39
Time and Space Complexity
Time Complexity: O(n)
, where n
is the length of the array.
The algorithm uses two pointers that traverse the array from opposite ends:
- The first pointer
i
starts from the beginning and moves forward whilearr[i] < arr[i + 1]
, which takes at mostO(n)
iterations - The second pointer
j
starts from the end and moves backward whilearr[j - 1] > arr[j]
, which also takes at mostO(n)
iterations - Since each element is visited at most once by either pointer, the total time complexity is
O(n)
Space Complexity: O(1)
The algorithm only uses a constant amount of extra space:
- Variables
n
,i
, andj
are the only additional space used - No additional data structures are created
- The space usage does not depend on the input size
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Incorrect Boundary Conditions in While Loops
The Problem: A common mistake is using incorrect boundary conditions in the while loops, such as:
# Incorrect versions: while left_pointer < n - 1 and arr[left_pointer] < arr[left_pointer + 1]: left_pointer += 1 while right_pointer > 0 and arr[right_pointer - 1] > arr[right_pointer]: right_pointer -= 1
Why It's Wrong:
- The first loop with
left_pointer < n - 1
would allow the peak to be at positionn - 1
(the last element) - The second loop with
right_pointer > 0
would allow the peak to be at position0
(the first element) - This violates the mountain array requirement that the peak cannot be at the boundaries
The Solution: Use the correct boundary conditions:
while left_pointer + 1 < n - 1 and arr[left_pointer] < arr[left_pointer + 1]: left_pointer += 1 while right_pointer - 1 > 0 and arr[right_pointer - 1] > arr[right_pointer]: right_pointer -= 1
Pitfall 2: Not Handling Arrays That Only Increase or Only Decrease
The Problem:
Forgetting that arrays like [1, 2, 3, 4]
(only increasing) or [4, 3, 2, 1]
(only decreasing) are not valid mountains. Some might assume that if one pointer moves, it's sufficient.
Why It Happens:
If the array only increases, left_pointer
will reach n - 2
but right_pointer
will stay at n - 1
. If the array only decreases, right_pointer
will reach 1
but left_pointer
will stay at 0
. In both cases, left_pointer != right_pointer
.
The Solution:
The final check return left_pointer == right_pointer
correctly handles this. Both pointers must meet at the same position for a valid mountain.
Pitfall 3: Confusing Strict Inequality with Non-Strict
The Problem:
Using <=
or >=
instead of <
and >
:
# Incorrect: while left_pointer + 1 < n - 1 and arr[left_pointer] <= arr[left_pointer + 1]: left_pointer += 1
Why It's Wrong:
Mountain arrays require strictly increasing and decreasing sequences. Equal consecutive elements like in [1, 2, 2, 1]
would make it invalid, but using <=
would incorrectly accept it.
The Solution:
Always use strict inequalities (<
and >
) to ensure no plateaus or equal consecutive elements are allowed.
Pitfall 4: Off-by-One Errors in Index Checking
The Problem:
Incorrectly checking array bounds when accessing arr[i + 1]
or arr[j - 1]
:
# Potential index out of bounds: while arr[left_pointer] < arr[left_pointer + 1]: # Missing boundary check left_pointer += 1
Why It's Dangerous:
Without proper boundary checks, arr[left_pointer + 1]
could cause an index out of bounds error when left_pointer
reaches n - 1
.
The Solution: Always include boundary checks before array access:
while left_pointer + 1 < n - 1 and arr[left_pointer] < arr[left_pointer + 1]: left_pointer += 1
Note that the boundary check must come before the array comparison due to short-circuit evaluation.
What is the best way of checking if an element exists in a sorted array once in terms of time complexity? Select the best that applies.
Recommended Readings
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
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!