Facebook Pixel

2865. Beautiful Towers I

Problem Description

You are given an array heights of n integers representing the number of bricks in n consecutive towers. Your task is to remove some bricks to form a mountain-shaped tower arrangement.

A mountain-shaped arrangement means:

  • The tower heights are non-decreasing from left until reaching a peak
  • The peak can be one or multiple consecutive towers with the same maximum height
  • After the peak, the tower heights are non-increasing toward the right

You can only remove bricks from towers (reduce their heights), not add bricks. The goal is to find the maximum possible sum of all tower heights after forming a valid mountain shape.

For example, if you have towers [5, 3, 4, 1, 1], one possible mountain arrangement could be [3, 3, 3, 1, 1] where the peak is at height 3, giving a sum of 11. Another arrangement could be [1, 1, 1, 1, 1] with sum 5. The algorithm needs to find the arrangement that maximizes this sum.

The solution approach enumerates each position as a potential peak of the mountain. For each potential peak position i, it:

  1. Starts with the height at position i as the peak height
  2. Expands leftward, ensuring each tower height doesn't exceed the previous one (maintaining non-decreasing property toward peak)
  3. Expands rightward, ensuring each tower height doesn't exceed the previous one (maintaining non-increasing property from peak)
  4. Calculates the total sum for this configuration
  5. Tracks the maximum sum across all possible peak positions
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is that every valid mountain must have a peak somewhere, and once we decide where the peak is, the rest of the arrangement is determined by a greedy strategy.

Think about it this way: if we fix position i as our peak with height maxHeights[i], what's the best we can do? Moving left from the peak, each tower can be at most as tall as the one to its right (to maintain the non-decreasing property toward the peak). So at each position going left, we take the minimum of the current tower's original height and the height we just assigned to its right neighbor. The same logic applies going right from the peak - each tower can be at most as tall as its left neighbor.

This greedy approach works because once we fix the peak, we want to maximize the height at every other position. The constraint is that we can't exceed the original height (we can only remove bricks) and we must maintain the mountain property. Taking the minimum at each step gives us the tallest possible height that satisfies both constraints.

Since we don't know which position should be the peak beforehand, we try all possibilities. For each position i from 0 to n-1, we assume it's the peak and calculate the maximum sum possible with that assumption. The answer is the maximum among all these sums.

The time complexity is O(n²) because for each of the n positions, we potentially scan all n positions to build the mountain. While this might seem expensive, it's acceptable for reasonable input sizes and guarantees we find the optimal solution.

Learn more about Stack and Monotonic Stack patterns.

Solution Approach

The implementation uses a brute force enumeration approach where we try each position as the potential peak of the mountain.

Here's how the algorithm works step by step:

  1. Initialize variables: We set ans = 0 to track the maximum sum found, and n = len(maxHeights) to store the array length.

  2. Enumerate each peak position: We iterate through each index i and its corresponding height x using enumerate(maxHeights). This position i will be treated as the peak of our mountain.

  3. Calculate the mountain sum for peak at position i:

    • Initialize y = x (current height limit) and t = x (total sum starting with peak height)
  4. Expand leftward from the peak (indices i-1 down to 0):

    for j in range(i - 1, -1, -1):
        y = min(y, maxHeights[j])
        t += y
    • We maintain y as the maximum allowed height for the current position
    • At each step, y = min(y, maxHeights[j]) ensures we don't exceed the original height and maintain non-decreasing property toward the peak
    • We add this height to our running sum t
  5. Expand rightward from the peak (indices i+1 to n-1):

    y = x  # Reset y to peak height
    for j in range(i + 1, n):
        y = min(y, maxHeights[j])
        t += y
    • Reset y back to the peak height x
    • Apply the same logic going right: each position gets min(y, maxHeights[j])
    • This maintains the non-increasing property from the peak
  6. Update the maximum: After calculating the sum t for peak at position i, we update ans = max(ans, t).

  7. Return the result: After trying all positions as potential peaks, return ans.

The algorithm essentially implements the greedy strategy for each fixed peak position - once we choose a peak, we greedily assign the maximum possible height to each other position while respecting the mountain constraints. By trying all possible peaks, we guarantee finding the optimal solution.

Ready to land your dream job?

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

Start Evaluator

Example Walkthrough

Let's walk through the algorithm with maxHeights = [5, 3, 4, 1, 1].

We'll try each position as the peak and calculate the resulting mountain sum.

Peak at index 0 (height = 5):

  • Start with peak: [5, _, _, _, _], sum = 5
  • Expand left: (no elements to the left)
  • Expand right from index 1:
    • Index 1: min(5, 3) = 3[5, 3, _, _, _]
    • Index 2: min(3, 4) = 3[5, 3, 3, _, _]
    • Index 3: min(3, 1) = 1[5, 3, 3, 1, _]
    • Index 4: min(1, 1) = 1[5, 3, 3, 1, 1]
  • Total sum = 5 + 3 + 3 + 1 + 1 = 13

Peak at index 1 (height = 3):

  • Start with peak: [_, 3, _, _, _], sum = 3
  • Expand left from index 0:
    • Index 0: min(3, 5) = 3[3, 3, _, _, _]
  • Expand right from index 2:
    • Index 2: min(3, 4) = 3[3, 3, 3, _, _]
    • Index 3: min(3, 1) = 1[3, 3, 3, 1, _]
    • Index 4: min(1, 1) = 1[3, 3, 3, 1, 1]
  • Total sum = 3 + 3 + 3 + 1 + 1 = 11

Peak at index 2 (height = 4):

  • Start with peak: [_, _, 4, _, _], sum = 4
  • Expand left from index 1:
    • Index 1: min(4, 3) = 3[_, 3, 4, _, _]
    • Index 0: min(3, 5) = 3[3, 3, 4, _, _]
  • Expand right from index 3:
    • Index 3: min(4, 1) = 1[3, 3, 4, 1, _]
    • Index 4: min(1, 1) = 1[3, 3, 4, 1, 1]
  • Total sum = 3 + 3 + 4 + 1 + 1 = 12

Peak at index 3 (height = 1):

  • Start with peak: [_, _, _, 1, _], sum = 1
  • Expand left from index 2:
    • Index 2: min(1, 4) = 1[_, _, 1, 1, _]
    • Index 1: min(1, 3) = 1[_, 1, 1, 1, _]
    • Index 0: min(1, 5) = 1[1, 1, 1, 1, _]
  • Expand right from index 4:
    • Index 4: min(1, 1) = 1[1, 1, 1, 1, 1]
  • Total sum = 1 + 1 + 1 + 1 + 1 = 5

Peak at index 4 (height = 1):

  • Start with peak: [_, _, _, _, 1], sum = 1
  • Expand left from index 3:
    • Index 3: min(1, 1) = 1[_, _, _, 1, 1]
    • Index 2: min(1, 4) = 1[_, _, 1, 1, 1]
    • Index 1: min(1, 3) = 1[_, 1, 1, 1, 1]
    • Index 0: min(1, 5) = 1[1, 1, 1, 1, 1]
  • Total sum = 1 + 1 + 1 + 1 + 1 = 5

Maximum sum = 13 (achieved with peak at index 0, resulting in [5, 3, 3, 1, 1])

Solution Implementation

1class Solution:
2    def maximumSumOfHeights(self, maxHeights: List[int]) -> int:
3        """
4        Find the maximum sum of heights for a mountain array.
5        A mountain array has heights that increase up to a peak and then decrease.
6        Each height must not exceed the corresponding value in maxHeights.
7      
8        Args:
9            maxHeights: List of maximum allowed heights at each position
10          
11        Returns:
12            Maximum possible sum of all heights in the mountain array
13        """
14        max_sum = 0
15        n = len(maxHeights)
16      
17        # Try each position as the peak of the mountain
18        for peak_idx, peak_height in enumerate(maxHeights):
19            # Initialize current height and total sum with peak value
20            current_height = peak_height
21            total_sum = peak_height
22          
23            # Build left side of mountain (decreasing from peak going left)
24            for j in range(peak_idx - 1, -1, -1):
25                # Height cannot exceed maxHeights[j] and must be non-increasing
26                current_height = min(current_height, maxHeights[j])
27                total_sum += current_height
28          
29            # Reset height for right side
30            current_height = peak_height
31          
32            # Build right side of mountain (decreasing from peak going right)
33            for j in range(peak_idx + 1, n):
34                # Height cannot exceed maxHeights[j] and must be non-increasing
35                current_height = min(current_height, maxHeights[j])
36                total_sum += current_height
37          
38            # Update maximum sum found so far
39            max_sum = max(max_sum, total_sum)
40      
41        return max_sum
42
1class Solution {
2    public long maximumSumOfHeights(List<Integer> maxHeights) {
3        long maxSum = 0;
4        int n = maxHeights.size();
5      
6        // Try each position as the peak of the mountain
7        for (int peakIndex = 0; peakIndex < n; ++peakIndex) {
8            int currentHeight = maxHeights.get(peakIndex);
9            long currentSum = currentHeight;
10          
11            // Build decreasing heights to the left of peak
12            int leftHeight = currentHeight;
13            for (int j = peakIndex - 1; j >= 0; --j) {
14                // Height cannot exceed the maximum allowed height at position j
15                // and must be non-increasing from the previous position
16                leftHeight = Math.min(leftHeight, maxHeights.get(j));
17                currentSum += leftHeight;
18            }
19          
20            // Build decreasing heights to the right of peak
21            int rightHeight = currentHeight;
22            for (int j = peakIndex + 1; j < n; ++j) {
23                // Height cannot exceed the maximum allowed height at position j
24                // and must be non-increasing from the previous position
25                rightHeight = Math.min(rightHeight, maxHeights.get(j));
26                currentSum += rightHeight;
27            }
28          
29            // Update the maximum sum found so far
30            maxSum = Math.max(maxSum, currentSum);
31        }
32      
33        return maxSum;
34    }
35}
36
1class Solution {
2public:
3    long long maximumSumOfHeights(vector<int>& maxHeights) {
4        long long maxSum = 0;
5        int n = maxHeights.size();
6      
7        // Try each position as the peak of the mountain
8        for (int peakIndex = 0; peakIndex < n; ++peakIndex) {
9            // Initialize current sum with the peak height
10            long long currentSum = maxHeights[peakIndex];
11            int currentHeight = maxHeights[peakIndex];
12          
13            // Build non-increasing sequence to the left of peak
14            for (int leftIndex = peakIndex - 1; leftIndex >= 0; --leftIndex) {
15                // Height cannot exceed previous height (maintaining non-increasing)
16                // and cannot exceed the maximum allowed height at this position
17                currentHeight = min(currentHeight, maxHeights[leftIndex]);
18                currentSum += currentHeight;
19            }
20          
21            // Reset height for right side traversal
22            currentHeight = maxHeights[peakIndex];
23          
24            // Build non-increasing sequence to the right of peak
25            for (int rightIndex = peakIndex + 1; rightIndex < n; ++rightIndex) {
26                // Height cannot exceed previous height (maintaining non-increasing)
27                // and cannot exceed the maximum allowed height at this position
28                currentHeight = min(currentHeight, maxHeights[rightIndex]);
29                currentSum += currentHeight;
30            }
31          
32            // Update maximum sum found so far
33            maxSum = max(maxSum, currentSum);
34        }
35      
36        return maxSum;
37    }
38};
39
1/**
2 * Finds the maximum sum of heights when building a mountain-shaped array
3 * where one peak element is chosen and heights decrease on both sides
4 * @param maxHeights - Array of maximum allowed heights for each position
5 * @returns Maximum possible sum of all heights
6 */
7function maximumSumOfHeights(maxHeights: number[]): number {
8    let maxSum: number = 0;
9    const arrayLength: number = maxHeights.length;
10  
11    // Try each position as the peak of the mountain
12    for (let peakIndex: number = 0; peakIndex < arrayLength; peakIndex++) {
13        const peakHeight: number = maxHeights[peakIndex];
14        let currentHeight: number = peakHeight;
15        let totalSum: number = peakHeight;
16      
17        // Build decreasing heights to the left of peak
18        for (let leftIndex: number = peakIndex - 1; leftIndex >= 0; leftIndex--) {
19            // Height cannot exceed the previous height or the max allowed height
20            currentHeight = Math.min(currentHeight, maxHeights[leftIndex]);
21            totalSum += currentHeight;
22        }
23      
24        // Reset height for building to the right
25        currentHeight = peakHeight;
26      
27        // Build decreasing heights to the right of peak
28        for (let rightIndex: number = peakIndex + 1; rightIndex < arrayLength; rightIndex++) {
29            // Height cannot exceed the previous height or the max allowed height
30            currentHeight = Math.min(currentHeight, maxHeights[rightIndex]);
31            totalSum += currentHeight;
32        }
33      
34        // Update maximum sum if current configuration is better
35        maxSum = Math.max(maxSum, totalSum);
36    }
37  
38    return maxSum;
39}
40

Time and Space Complexity

The time complexity of this algorithm is O(n²), where n is the length of the maxHeights array.

This is because:

  • The outer loop iterates through each element in maxHeights, running n times
  • For each position i, there are two inner loops:
    • The first inner loop iterates from i-1 down to 0, which can be up to i iterations
    • The second inner loop iterates from i+1 to n-1, which can be up to n-i-1 iterations
  • In the worst case, when i is in the middle, the total iterations for both inner loops is approximately n-1
  • Therefore, the overall time complexity is O(n) × O(n) = O(n²)

The space complexity is O(1) as the algorithm only uses a constant amount of extra space:

  • Variables ans, n, i, x, y, t, and j are all scalar values
  • No additional data structures are created that scale with the input size
  • The algorithm modifies values in-place without requiring auxiliary space proportional to n

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

Common Pitfalls

Pitfall 1: Misunderstanding the Mountain Shape Definition

The Problem: A common misunderstanding is thinking that the mountain must have a single peak tower, when in fact multiple consecutive towers can share the same peak height. Developers might incorrectly implement strict inequality checks (heights must strictly increase then strictly decrease) instead of allowing plateaus at the peak.

Example of Incorrect Implementation:

# WRONG: Enforcing strict increase/decrease
for j in range(peak_idx - 1, -1, -1):
    current_height = min(current_height - 1, maxHeights[j])  # Forces decrease
    total_sum += current_height

Correct Approach:

# CORRECT: Allowing equal heights (plateau)
for j in range(peak_idx - 1, -1, -1):
    current_height = min(current_height, maxHeights[j])  # Allows plateau
    total_sum += current_height

Pitfall 2: Not Resetting the Current Height Between Left and Right Expansions

The Problem: After building the left side of the mountain, forgetting to reset current_height back to peak_height before building the right side. This causes the right side to start with the wrong height value.

Example of Incorrect Implementation:

# Building left side
current_height = peak_height
for j in range(peak_idx - 1, -1, -1):
    current_height = min(current_height, maxHeights[j])
    total_sum += current_height

# WRONG: Forgot to reset current_height
# current_height is now the leftmost tower's height, not peak_height!
for j in range(peak_idx + 1, n):
    current_height = min(current_height, maxHeights[j])
    total_sum += current_height

Correct Approach:

# Building left side
current_height = peak_height
for j in range(peak_idx - 1, -1, -1):
    current_height = min(current_height, maxHeights[j])
    total_sum += current_height

# CORRECT: Reset to peak height before building right side
current_height = peak_height
for j in range(peak_idx + 1, n):
    current_height = min(current_height, maxHeights[j])
    total_sum += current_height

Pitfall 3: Off-by-One Errors in Range Iterations

The Problem: Using incorrect range boundaries when iterating left or right from the peak, potentially missing towers or including the peak tower twice.

Example of Incorrect Implementation:

# WRONG: Including peak_idx in left iteration
for j in range(peak_idx, -1, -1):  # This includes peak_idx again!
    current_height = min(current_height, maxHeights[j])
    total_sum += current_height

# WRONG: Starting from peak_idx instead of peak_idx + 1
for j in range(peak_idx, n):  # This includes peak_idx again!
    current_height = min(current_height, maxHeights[j])
    total_sum += current_height

Correct Approach:

# CORRECT: Start from peak_idx - 1 for left side
for j in range(peak_idx - 1, -1, -1):
    current_height = min(current_height, maxHeights[j])
    total_sum += current_height

# CORRECT: Start from peak_idx + 1 for right side
for j in range(peak_idx + 1, n):
    current_height = min(current_height, maxHeights[j])
    total_sum += current_height

Pitfall 4: Trying to Optimize Prematurely with Incorrect Assumptions

The Problem: Attempting to optimize by assuming the peak should always be at the tallest tower in the array, or making other incorrect assumptions about where the optimal peak must be located.

Example of Incorrect Optimization:

# WRONG: Only checking the tallest tower as peak
max_height_idx = maxHeights.index(max(maxHeights))
# Only calculate sum for this single peak position

Why This Fails: The optimal peak might not be at the tallest tower. For example, with maxHeights = [1, 5, 2, 5, 1], the optimal solution uses index 2 (height 2) as the peak, giving [1, 2, 2, 2, 1] with sum 8, rather than using either height-5 tower as the peak.

Correct Approach: Check all positions as potential peaks, as the original solution does. The O(n²) complexity is necessary for correctness in this brute-force approach.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of these pictures shows the visit order of a depth-first search?


Recommended Readings

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

Load More