Facebook Pixel

1671. Minimum Number of Removals to Make Mountain Array

Problem Description

You need to find the minimum number of elements to remove from an array to make it a "mountain array".

A mountain array is defined as an array that:

  • Has at least 3 elements
  • Has a peak element at some index i (where 0 < i < arr.length - 1)
  • All elements before the peak are in strictly increasing order: arr[0] < arr[1] < ... < arr[i-1] < arr[i]
  • All elements after the peak are in strictly decreasing order: arr[i] > arr[i+1] > ... > arr[arr.length-1]

Given an integer array nums, you need to determine the minimum number of elements that must be removed to transform it into a valid mountain array.

The solution uses dynamic programming to find the longest mountain subsequence. It calculates:

  • left[i]: the length of the longest increasing subsequence ending at position i
  • right[i]: the length of the longest decreasing subsequence starting at position i

For each position i that can serve as a peak (where left[i] > 1 and right[i] > 1), the length of the mountain subsequence with peak at i is left[i] + right[i] - 1. The answer is the total length n minus the maximum mountain subsequence length found.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is to think about what we want to keep rather than what we want to remove. Instead of directly calculating removals, we can find the longest valid mountain subsequence and then subtract its length from the total array length.

A mountain has two parts: an ascending slope and a descending slope. Any valid mountain subsequence must have elements that form an increasing sequence up to a peak, then a decreasing sequence down from that peak.

For each potential peak position i, we need to know:

  • How long can the ascending part be if it ends at position i?
  • How long can the descending part be if it starts at position i?

This naturally leads us to the Longest Increasing Subsequence (LIS) problem. We can compute:

  • left[i]: the LIS ending at position i (the ascending part)
  • right[i]: the Longest Decreasing Subsequence starting at position i (the descending part)

If position i is a valid peak, it must have at least one element before it in the ascending part (left[i] > 1) and at least one element after it in the descending part (right[i] > 1).

The total length of a mountain with peak at i would be left[i] + right[i] - 1 (we subtract 1 because the peak is counted in both sequences).

By finding the maximum possible mountain length across all valid peaks, we can determine the minimum removals needed: n - max(mountain_length).

Learn more about Greedy, Binary Search and Dynamic Programming patterns.

Solution Approach

The solution uses dynamic programming to compute the longest increasing and decreasing subsequences for each position.

Step 1: Initialize arrays

  • Create left[i] array to store the length of the longest increasing subsequence ending at position i
  • Create right[i] array to store the length of the longest decreasing subsequence starting at position i
  • Initialize both arrays with 1s (each element is a subsequence of length 1 by itself)

Step 2: Compute longest increasing subsequences (left to right)

for i in range(1, n):
    for j in range(i):
        if nums[i] > nums[j]:
            left[i] = max(left[i], left[j] + 1)

For each position i, check all previous positions j < i. If nums[i] > nums[j], we can extend the increasing subsequence ending at j by including nums[i]. Take the maximum length possible.

Step 3: Compute longest decreasing subsequences (right to left)

for i in range(n - 2, -1, -1):
    for j in range(i + 1, n):
        if nums[i] > nums[j]:
            right[i] = max(right[i], right[j] + 1)

For each position i, check all positions j > i. If nums[i] > nums[j], we can extend the decreasing subsequence starting at j by including nums[i] at the beginning. Take the maximum length possible.

Step 4: Find the maximum mountain length

return n - max(a + b - 1 for a, b in zip(left, right) if a > 1 and b > 1)

For each position that can be a valid peak (left[i] > 1 and right[i] > 1), calculate the mountain length as left[i] + right[i] - 1. The minimum removals equal n minus the maximum mountain length found.

The time complexity is O(n²) due to the nested loops in computing the subsequences, and the space complexity is O(n) for storing the left and right arrays.

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through the solution with a concrete example: nums = [2, 1, 5, 6, 2, 3, 1]

Step 1: Initialize arrays

  • n = 7
  • left = [1, 1, 1, 1, 1, 1, 1] (each element forms a subsequence of length 1)
  • right = [1, 1, 1, 1, 1, 1, 1]

Step 2: Compute longest increasing subsequences (left array)

For i = 1: nums[1]=1

  • Check j=0: nums[1]=1 not > nums[0]=2, skip
  • left[1] = 1

For i = 2: nums[2]=5

  • Check j=0: nums[2]=5 > nums[0]=2, so left[2] = max(1, left[0]+1) = 2
  • Check j=1: nums[2]=5 > nums[1]=1, so left[2] = max(2, left[1]+1) = 2
  • left[2] = 2

For i = 3: nums[3]=6

  • Check j=0: nums[3]=6 > nums[0]=2, so left[3] = max(1, left[0]+1) = 2
  • Check j=1: nums[3]=6 > nums[1]=1, so left[3] = max(2, left[1]+1) = 2
  • Check j=2: nums[3]=6 > nums[2]=5, so left[3] = max(2, left[2]+1) = 3
  • left[3] = 3

Continuing this process:

  • left[4] = 2 (can extend from nums[0]=2 or nums[1]=1)
  • left[5] = 3 (best is 1→2→3 or 2→3)
  • left[6] = 1 (1 is smallest, can't extend any sequence)

Final: left = [1, 1, 2, 3, 2, 3, 1]

Step 3: Compute longest decreasing subsequences (right array)

For i = 5: nums[5]=3

  • Check j=6: nums[5]=3 > nums[6]=1, so right[5] = max(1, right[6]+1) = 2
  • right[5] = 2

For i = 4: nums[4]=2

  • Check j=5: nums[4]=2 not > nums[5]=3, skip
  • Check j=6: nums[4]=2 > nums[6]=1, so right[4] = max(1, right[6]+1) = 2
  • right[4] = 2

For i = 3: nums[3]=6

  • Check j=4: nums[3]=6 > nums[4]=2, so right[3] = max(1, right[4]+1) = 3
  • Check j=5: nums[3]=6 > nums[5]=3, so right[3] = max(3, right[5]+1) = 3
  • Check j=6: nums[3]=6 > nums[6]=1, so right[3] = max(3, right[6]+1) = 3
  • right[3] = 3

Continuing backwards:

  • right[2] = 4 (can go 5→2→1 or 5→3→1)
  • right[1] = 1 (1 cannot start a decreasing sequence to any element after it)
  • right[0] = 5 (can go 2→1 directly, or through other paths)

Final: right = [5, 1, 4, 3, 2, 2, 1]

Step 4: Find maximum mountain length

Check each position as a potential peak:

  • i=0: left[0]=1, right[0]=5 → Not valid (left[0] ≤ 1)
  • i=1: left[1]=1, right[1]=1 → Not valid (both ≤ 1)
  • i=2: left[2]=2, right[2]=4 → Valid! Mountain length = 2 + 4 - 1 = 5
  • i=3: left[3]=3, right[3]=3 → Valid! Mountain length = 3 + 3 - 1 = 5
  • i=4: left[4]=2, right[4]=2 → Valid! Mountain length = 2 + 2 - 1 = 3
  • i=5: left[5]=3, right[5]=2 → Valid! Mountain length = 3 + 2 - 1 = 4
  • i=6: left[6]=1, right[6]=1 → Not valid (both ≤ 1)

Maximum mountain length = 5

Answer: 7 - 5 = 2 (minimum removals needed)

One possible mountain subsequence of length 5 would be [2, 5, 6, 2, 1] with peak at 6.

Solution Implementation

1class Solution:
2    def minimumMountainRemovals(self, nums: List[int]) -> int:
3        n = len(nums)
4      
5        # left[i] stores the length of the longest increasing subsequence ending at index i
6        left = [1] * n
7      
8        # right[i] stores the length of the longest decreasing subsequence starting at index i
9        right = [1] * n
10      
11        # Calculate LIS (Longest Increasing Subsequence) for each position from left
12        for i in range(1, n):
13            for j in range(i):
14                if nums[i] > nums[j]:
15                    left[i] = max(left[i], left[j] + 1)
16      
17        # Calculate LIS from right (which gives us longest decreasing subsequence from left perspective)
18        for i in range(n - 2, -1, -1):
19            for j in range(i + 1, n):
20                if nums[i] > nums[j]:
21                    right[i] = max(right[i], right[j] + 1)
22      
23        # Find the maximum mountain length
24        # A valid mountain must have left[i] > 1 and right[i] > 1 (peak cannot be at endpoints)
25        # Mountain length at position i = left[i] + right[i] - 1 (subtract 1 as peak is counted twice)
26        max_mountain_length = max(
27            left[i] + right[i] - 1 
28            for i in range(n) 
29            if left[i] > 1 and right[i] > 1
30        )
31      
32        # Minimum removals = total elements - maximum mountain length
33        return n - max_mountain_length
34
1class Solution {
2    public int minimumMountainRemovals(int[] nums) {
3        int n = nums.length;
4      
5        // Arrays to store the length of longest increasing subsequence
6        // ending at each index (from left) and starting at each index (from right)
7        int[] lisFromLeft = new int[n];
8        int[] lisFromRight = new int[n];
9      
10        // Initialize all positions with 1 (each element is a subsequence of length 1)
11        Arrays.fill(lisFromLeft, 1);
12        Arrays.fill(lisFromRight, 1);
13      
14        // Calculate longest increasing subsequence ending at each position
15        // Dynamic programming: for each position i, check all previous positions j
16        for (int i = 1; i < n; ++i) {
17            for (int j = 0; j < i; ++j) {
18                // If current element is greater than previous element,
19                // we can extend the increasing subsequence
20                if (nums[i] > nums[j]) {
21                    lisFromLeft[i] = Math.max(lisFromLeft[i], lisFromLeft[j] + 1);
22                }
23            }
24        }
25      
26        // Calculate longest decreasing subsequence starting at each position
27        // (equivalently, longest increasing subsequence from right to left)
28        for (int i = n - 2; i >= 0; --i) {
29            for (int j = i + 1; j < n; ++j) {
30                // If current element is greater than next element,
31                // we can extend the decreasing subsequence
32                if (nums[i] > nums[j]) {
33                    lisFromRight[i] = Math.max(lisFromRight[i], lisFromRight[j] + 1);
34                }
35            }
36        }
37      
38        // Find the maximum length of a mountain subsequence
39        int maxMountainLength = 0;
40        for (int i = 0; i < n; ++i) {
41            // A valid mountain peak must have at least one element on each side
42            // (lisFromLeft[i] > 1 means there's at least one element before i in the increasing part)
43            // (lisFromRight[i] > 1 means there's at least one element after i in the decreasing part)
44            if (lisFromLeft[i] > 1 && lisFromRight[i] > 1) {
45                // Mountain length = increasing part + decreasing part - 1 (peak counted twice)
46                int mountainLength = lisFromLeft[i] + lisFromRight[i] - 1;
47                maxMountainLength = Math.max(maxMountainLength, mountainLength);
48            }
49        }
50      
51        // Minimum removals = total elements - maximum mountain length
52        return n - maxMountainLength;
53    }
54}
55
1class Solution {
2public:
3    int minimumMountainRemovals(vector<int>& nums) {
4        int n = nums.size();
5      
6        // leftLIS[i] stores the length of longest increasing subsequence ending at index i
7        vector<int> leftLIS(n, 1);
8      
9        // rightLIS[i] stores the length of longest decreasing subsequence starting at index i
10        vector<int> rightLIS(n, 1);
11      
12        // Calculate LIS from left to right for each position
13        for (int i = 1; i < n; ++i) {
14            for (int j = 0; j < i; ++j) {
15                if (nums[i] > nums[j]) {
16                    leftLIS[i] = max(leftLIS[i], leftLIS[j] + 1);
17                }
18            }
19        }
20      
21        // Calculate LIS from right to left (decreasing subsequence) for each position
22        for (int i = n - 2; i >= 0; --i) {
23            for (int j = i + 1; j < n; ++j) {
24                if (nums[i] > nums[j]) {
25                    rightLIS[i] = max(rightLIS[i], rightLIS[j] + 1);
26                }
27            }
28        }
29      
30        // Find the maximum length of mountain subsequence
31        int maxMountainLength = 0;
32        for (int i = 0; i < n; ++i) {
33            // A valid mountain peak must have at least one element on both sides
34            // leftLIS[i] > 1 ensures there's at least one element before peak
35            // rightLIS[i] > 1 ensures there's at least one element after peak
36            if (leftLIS[i] > 1 && rightLIS[i] > 1) {
37                // Total mountain length with peak at i
38                // Subtract 1 because peak is counted in both leftLIS and rightLIS
39                maxMountainLength = max(maxMountainLength, leftLIS[i] + rightLIS[i] - 1);
40            }
41        }
42      
43        // Minimum removals = total elements - maximum mountain length
44        return n - maxMountainLength;
45    }
46};
47
1/**
2 * Finds the minimum number of elements to remove to make the array a mountain array.
3 * A mountain array has at least 3 elements where elements strictly increase to a peak,
4 * then strictly decrease.
5 * 
6 * @param nums - The input array of numbers
7 * @returns The minimum number of elements to remove
8 */
9function minimumMountainRemovals(nums: number[]): number {
10    const arrayLength: number = nums.length;
11  
12    // leftLIS[i] stores the length of longest increasing subsequence ending at index i
13    const leftLIS: number[] = Array(arrayLength).fill(1);
14  
15    // rightLIS[i] stores the length of longest decreasing subsequence starting at index i
16    const rightLIS: number[] = Array(arrayLength).fill(1);
17  
18    // Calculate longest increasing subsequence for each position from left
19    for (let currentIndex = 1; currentIndex < arrayLength; ++currentIndex) {
20        for (let previousIndex = 0; previousIndex < currentIndex; ++previousIndex) {
21            if (nums[currentIndex] > nums[previousIndex]) {
22                leftLIS[currentIndex] = Math.max(
23                    leftLIS[currentIndex], 
24                    leftLIS[previousIndex] + 1
25                );
26            }
27        }
28    }
29  
30    // Calculate longest decreasing subsequence for each position from right
31    for (let currentIndex = arrayLength - 2; currentIndex >= 0; --currentIndex) {
32        for (let nextIndex = currentIndex + 1; nextIndex < arrayLength; ++nextIndex) {
33            if (nums[currentIndex] > nums[nextIndex]) {
34                rightLIS[currentIndex] = Math.max(
35                    rightLIS[currentIndex], 
36                    rightLIS[nextIndex] + 1
37                );
38            }
39        }
40    }
41  
42    // Find the maximum length of mountain subsequence
43    let maxMountainLength: number = 0;
44  
45    for (let peakIndex = 0; peakIndex < arrayLength; ++peakIndex) {
46        // A valid mountain peak must have at least one element on each side
47        if (leftLIS[peakIndex] > 1 && rightLIS[peakIndex] > 1) {
48            // Mountain length = left side + right side - 1 (peak counted twice)
49            const mountainLength: number = leftLIS[peakIndex] + rightLIS[peakIndex] - 1;
50            maxMountainLength = Math.max(maxMountainLength, mountainLength);
51        }
52    }
53  
54    // Minimum removals = total elements - maximum mountain length
55    return arrayLength - maxMountainLength;
56}
57

Time and Space Complexity

The time complexity is O(n²), where n is the length of the array nums. This is because:

  • The first nested loop (computing left array) iterates through each element i from 1 to n-1, and for each i, it checks all previous elements j from 0 to i-1, resulting in O(n²) operations.
  • The second nested loop (computing right array) iterates through each element i from n-2 to 0, and for each i, it checks all subsequent elements j from i+1 to n-1, also resulting in O(n²) operations.
  • The final list comprehension to find the maximum value runs in O(n) time.
  • Overall: O(n²) + O(n²) + O(n) = O(n²)

The space complexity is O(n), where n is the length of the array nums. This is because:

  • The left array uses O(n) space to store the length of the longest increasing subsequence ending at each position.
  • The right array uses O(n) space to store the length of the longest decreasing subsequence starting at each position.
  • The list comprehension creates temporary values but doesn't store them, so it doesn't add to the space complexity.
  • Overall: O(n) + O(n) = O(n)

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Forgetting to validate peak position constraints

A common mistake is not checking that left[i] > 1 and right[i] > 1 when finding the maximum mountain length. This validation ensures:

  • The peak is not at the first or last position (mountain needs at least one element on each side)
  • There actually exists an increasing sequence before the peak and a decreasing sequence after it

Incorrect approach:

# This would include invalid mountains with peaks at endpoints
max_mountain_length = max(left[i] + right[i] - 1 for i in range(n))

Correct approach:

max_mountain_length = max(
    left[i] + right[i] - 1 
    for i in range(n) 
    if left[i] > 1 and right[i] > 1
)

2. Double-counting the peak element

When calculating the mountain length, remember that the peak element is counted in both left[i] (as the end of increasing subsequence) and right[i] (as the start of decreasing subsequence). Failing to subtract 1 leads to an incorrect mountain length.

Incorrect:

mountain_length = left[i] + right[i]  # Peak counted twice!

Correct:

mountain_length = left[i] + right[i] - 1  # Subtract 1 for the peak

3. Using non-strict comparisons

Mountain arrays require strictly increasing and strictly decreasing sequences. Using >= or <= instead of > would allow duplicate values, violating the mountain array definition.

Incorrect:

if nums[i] >= nums[j]:  # This allows equal elements
    left[i] = max(left[i], left[j] + 1)

Correct:

if nums[i] > nums[j]:  # Strictly greater for valid mountain
    left[i] = max(left[i], left[j] + 1)

4. Edge case: No valid mountain exists

If the array cannot form any valid mountain (e.g., all elements are equal or monotonic), the code might fail. Always ensure the generator expression has valid values:

Safe implementation:

valid_mountains = [
    left[i] + right[i] - 1 
    for i in range(n) 
    if left[i] > 1 and right[i] > 1
]

if not valid_mountains:
    # Handle case where no valid mountain can be formed
    # This might mean removing all but 3 elements in worst case
    return n - 3 if n >= 3 else 0

return n - max(valid_mountains)
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

What's the output of running the following function using input [30, 20, 10, 100, 33, 12]?

1def fun(arr: List[int]) -> List[int]:
2    import heapq
3    heapq.heapify(arr)
4    res = []
5    for i in range(3):
6        res.append(heapq.heappop(arr))
7    return res
8
1public static int[] fun(int[] arr) {
2    int[] res = new int[3];
3    PriorityQueue<Integer> heap = new PriorityQueue<>();
4    for (int i = 0; i < arr.length; i++) {
5        heap.add(arr[i]);
6    }
7    for (int i = 0; i < 3; i++) {
8        res[i] = heap.poll();
9    }
10    return res;
11}
12
1class HeapItem {
2    constructor(item, priority = item) {
3        this.item = item;
4        this.priority = priority;
5    }
6}
7
8class MinHeap {
9    constructor() {
10        this.heap = [];
11    }
12
13    push(node) {
14        // insert the new node at the end of the heap array
15        this.heap.push(node);
16        // find the correct position for the new node
17        this.bubble_up();
18    }
19
20    bubble_up() {
21        let index = this.heap.length - 1;
22
23        while (index > 0) {
24            const element = this.heap[index];
25            const parentIndex = Math.floor((index - 1) / 2);
26            const parent = this.heap[parentIndex];
27
28            if (parent.priority <= element.priority) break;
29            // if the parent is bigger than the child then swap the parent and child
30            this.heap[index] = parent;
31            this.heap[parentIndex] = element;
32            index = parentIndex;
33        }
34    }
35
36    pop() {
37        const min = this.heap[0];
38        this.heap[0] = this.heap[this.size() - 1];
39        this.heap.pop();
40        this.bubble_down();
41        return min;
42    }
43
44    bubble_down() {
45        let index = 0;
46        let min = index;
47        const n = this.heap.length;
48
49        while (index < n) {
50            const left = 2 * index + 1;
51            const right = left + 1;
52
53            if (left < n && this.heap[left].priority < this.heap[min].priority) {
54                min = left;
55            }
56            if (right < n && this.heap[right].priority < this.heap[min].priority) {
57                min = right;
58            }
59            if (min === index) break;
60            [this.heap[min], this.heap[index]] = [this.heap[index], this.heap[min]];
61            index = min;
62        }
63    }
64
65    peek() {
66        return this.heap[0];
67    }
68
69    size() {
70        return this.heap.length;
71    }
72}
73
74function fun(arr) {
75    const heap = new MinHeap();
76    for (const x of arr) {
77        heap.push(new HeapItem(x));
78    }
79    const res = [];
80    for (let i = 0; i < 3; i++) {
81        res.push(heap.pop().item);
82    }
83    return res;
84}
85

Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More