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:
- Starts with the height at position
i
as the peak height - Expands leftward, ensuring each tower height doesn't exceed the previous one (maintaining non-decreasing property toward peak)
- Expands rightward, ensuring each tower height doesn't exceed the previous one (maintaining non-increasing property from peak)
- Calculates the total sum for this configuration
- Tracks the maximum sum across all possible peak positions
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:
-
Initialize variables: We set
ans = 0
to track the maximum sum found, andn = len(maxHeights)
to store the array length. -
Enumerate each peak position: We iterate through each index
i
and its corresponding heightx
usingenumerate(maxHeights)
. This positioni
will be treated as the peak of our mountain. -
Calculate the mountain sum for peak at position i:
- Initialize
y = x
(current height limit) andt = x
(total sum starting with peak height)
- Initialize
-
Expand leftward from the peak (indices
i-1
down to0
):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
- We maintain
-
Expand rightward from the peak (indices
i+1
ton-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 heightx
- Apply the same logic going right: each position gets
min(y, maxHeights[j])
- This maintains the non-increasing property from the peak
- Reset
-
Update the maximum: After calculating the sum
t
for peak at positioni
, we updateans = max(ans, t)
. -
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 EvaluatorExample 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]
- Index 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, _, _, _]
- Index 0:
- 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]
- Index 2:
- 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, _, _]
- Index 1:
- 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]
- Index 3:
- 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, _]
- Index 2:
- Expand right from index 4:
- Index 4:
min(1, 1) = 1
→[1, 1, 1, 1, 1]
- Index 4:
- 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]
- Index 3:
- 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
, runningn
times - For each position
i
, there are two inner loops:- The first inner loop iterates from
i-1
down to0
, which can be up toi
iterations - The second inner loop iterates from
i+1
ton-1
, which can be up ton-i-1
iterations
- The first inner loop iterates from
- In the worst case, when
i
is in the middle, the total iterations for both inner loops is approximatelyn-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
, andj
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.
Which of these pictures shows the visit order of a depth-first search?
Recommended Readings
Stack Intro Imagine you have a pile of books on your desk If you want to add a new book you place it on top If you want to read a book you take it from the top And if you simply want to see which book is on the top you
Monotonic Stack Deque Intro If you'd prefer a video div class responsive iframe iframe src https www youtube com embed Dq_ObZwTY_Q title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div The word monotonic means a list or
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!