Beautiful Towers II
MedPrompt
You are given a 0-indexed array maxHeights of n integers.
You are tasked with building n towers in the coordinate line. The ith tower is built at coordinate i and has a height of heights[i].
A configuration of towers is beautiful if the following conditions hold:
1 <= heights[i] <= maxHeights[i]heightsis a mountain array.
Array heights is a mountain if there exists an index i such that:
- For all
0 < j <= i,heights[j - 1] <= heights[j] - For all
i <= k < n - 1,heights[k + 1] <= heights[k]
Return the maximum possible sum of heights of a beautiful configuration of towers.
Example 1:
Input: maxHeights = [5,3,4,1,1]
Output: 13
Explanation: One beautiful configuration with a maximum sum is heights = [5,3,3,1,1]. This configuration is beautiful since:
- 1 <= heights[i] <= maxHeights[i]
- heights is a mountain of peak i = 0.
It can be shown that there exists no other beautiful configuration with a sum of heights greater than 13.Example 2:
Input: maxHeights = [6,5,3,9,2,7]
Output: 22
Explanation: One beautiful configuration with a maximum sum is heights = [3,3,3,9,2,2]. This configuration is beautiful since:
- 1 <= heights[i] <= maxHeights[i]
- heights is a mountain of peak i = 3.
It can be shown that there exists no other beautiful configuration with a sum of heights greater than 22.Example 3:
Input: maxHeights = [3,2,5,5,2,3]
Output: 18
Explanation: One beautiful configuration with a maximum sum is heights = [2,2,5,5,2,2]. This configuration is beautiful since:
- 1 <= heights[i] <= maxHeights[i]
- heights is a mountain of peak i = 2.
Note that, for this configuration, i = 3 can also be considered a peak.
It can be shown that there exists no other beautiful configuration with a sum of heights greater than 18.
Constraints:
1 <= n == maxHeights.length <= 1051 <= maxHeights[i] <= 109
Approaches
2 approaches with complexity analysis and trade-offs.
The O(N^2) brute-force approach is slow because it repeatedly calculates sums for overlapping subarrays. We can optimize this by pre-calculating these sums. The key insight is that the total sum for a mountain with a peak at i can be decomposed into two independent subproblems: the maximum sum of a non-decreasing sequence ending at i and the maximum sum of a non-increasing sequence starting at i. These two sets of sums (for all i) can be computed efficiently in O(N) time using a monotonic stack. After pre-computation, we can find the maximum total sum in a final O(N) pass.
Algorithm
- Define two arrays,
leftSumandrightSum, of sizento store precomputed sums. - Calculate
leftSum:leftSum[i]will store the maximum sum of a valid non-decreasing sequence of towers onmaxHeights[0...i]withias the peak.- Use a monotonic stack (storing indices of
maxHeightswith increasing values) and iterate fromi = 0ton-1. - For each
i, pop from the stack whilemaxHeights[stack.peek()] >= maxHeights[i]. - The sum
leftSum[i]can then be calculated in O(1) using the previously computed sum for the new stack top (leftSum[p]) and the valuemaxHeights[i].
- Use a monotonic stack (storing indices of
- Calculate
rightSum:rightSum[i]will store the maximum sum of a valid non-increasing sequence of towers onmaxHeights[i...n-1]withias the peak.- This is symmetric to the
leftSumcalculation. Use a monotonic stack and iterate fromi = n-1down to0.
- This is symmetric to the
- Combine Results: The maximum sum for a mountain with peak
iisleftSum[i] + rightSum[i] - maxHeights[i](subtractingmaxHeights[i]because it's included in both sums). - Iterate through all
ifrom0ton-1, calculate this combined sum, and find the maximum value. This will be the final answer.
Walkthrough
This approach is based on dynamic programming and a monotonic stack. We pre-calculate two arrays:
-
leftSum[i]: The maximum sum of a beautiful tower configuration for the prefixmaxHeights[0...i]where the heights are non-decreasing (i.e.,iis the peak of this prefix). We can compute this for alliin O(N) time. We iterate from left to right, maintaining a monotonic stack of indices wheremaxHeightsvalues are increasing. For eachi, we find the previous indexpwheremaxHeights[p] < maxHeights[i]. All towers betweenpandiwill have heightmaxHeights[i]. The total sumleftSum[i]can be derived fromleftSum[p]. -
rightSum[i]: Similarly, this is the maximum sum for the suffixmaxHeights[i...n-1]where heights are non-increasing. This is calculated symmetrically by iterating from right to left.
Once both leftSum and rightSum arrays are populated, we can find the maximum possible sum for a mountain with a peak at index i by the formula: leftSum[i] + rightSum[i] - maxHeights.get(i). We subtract maxHeights.get(i) because it was counted in both leftSum[i] and rightSum[i]. The final answer is the maximum value of this expression over all i from 0 to n-1.
import java.util.List;import java.util.Stack; class Solution { public long maximumSumOfHeights(List<Integer> maxHeights) { int n = maxHeights.size(); long[] leftSum = new long[n]; Stack<Integer> stack = new Stack<>(); for (int i = 0; i < n; i++) { long currentMaxHeight = maxHeights.get(i); while (!stack.isEmpty() && maxHeights.get(stack.peek()) >= currentMaxHeight) { stack.pop(); } if (stack.isEmpty()) { leftSum[i] = (long)(i + 1) * currentMaxHeight; } else { int prevSmallerIndex = stack.peek(); leftSum[i] = leftSum[prevSmallerIndex] + (long)(i - prevSmallerIndex) * currentMaxHeight; } stack.push(i); } long[] rightSum = new long[n]; stack.clear(); for (int i = n - 1; i >= 0; i--) { long currentMaxHeight = maxHeights.get(i); while (!stack.isEmpty() && maxHeights.get(stack.peek()) >= currentMaxHeight) { stack.pop(); } if (stack.isEmpty()) { rightSum[i] = (long)(n - i) * currentMaxHeight; } else { int nextSmallerIndex = stack.peek(); rightSum[i] = rightSum[nextSmallerIndex] + (long)(nextSmallerIndex - i) * currentMaxHeight; } stack.push(i); } long maxTotalSum = 0; for (int i = 0; i < n; i++) { maxTotalSum = Math.max(maxTotalSum, leftSum[i] + rightSum[i] - maxHeights.get(i)); } return maxTotalSum; }}Complexity
Time
O(N). Calculating `leftSum` takes O(N), `rightSum` takes O(N), and the final pass to combine them takes O(N). Each element is pushed and popped from the stack at most once.
Space
O(N), for storing the `leftSum` and `rightSum` arrays, and for the stack which can grow up to size N in the worst case.
Trade-offs
Pros
Highly efficient with a linear time complexity.
Passes the given constraints with ease.
Cons
More complex to understand and implement correctly compared to the brute-force approach.
Requires knowledge of the monotonic stack data structure and its application.
This approach directly translates the problem definition into a solution. We consider every possible index as the peak of the mountain. For each potential peak, we construct the optimal heights array and calculate its sum. The optimal construction for a fixed peak i involves setting heights[i] = maxHeights[i] and then greedily choosing the largest possible heights for the towers to the left and right while satisfying the mountain and maxHeights constraints. The final answer is the maximum sum found among all possible peak choices.
Algorithm
- Initialize a variable
maxTotalSumto 0. - Iterate through each index
ifrom0ton-1, considering it as the potential peak of the mountain array. - For each
i, calculate the sum of heights for a beautiful configuration withias the peak.- Initialize
currentSumwithmaxHeights[i], as the peak tower will have its maximum possible height. - Initialize a variable
prevHeighttomaxHeights[i]. - Iterate backwards from
j = i - 1to0to calculate the sum of the left side (non-decreasing part).- The height at
jismin(maxHeights[j], prevHeight). - Add this height to
currentSumand updateprevHeight.
- The height at
- Reset
prevHeighttomaxHeights[i]. - Iterate forwards from
k = i + 1ton-1to calculate the sum of the right side (non-increasing part).- The height at
kismin(maxHeights[k], prevHeight). - Add this height to
currentSumand updateprevHeight.
- The height at
- Initialize
- After calculating the
currentSumfor the peaki, updatemaxTotalSum = max(maxTotalSum, currentSum). - After iterating through all possible peaks,
maxTotalSumwill hold the result.
Walkthrough
The core idea is to iterate through all possible indices i from 0 to n-1 and assume each one is the peak of the mountain. When i is the peak, to maximize the total sum, we should set heights[i] to its maximum possible value, which is maxHeights.get(i).
For the left side of the peak (j < i), the heights must be non-decreasing, i.e., heights[j] <= heights[j+1]. To maximize the sum, we set heights[j] to the largest value possible, which is min(maxHeights.get(j), heights[j+1]). We can calculate these heights and their sum by iterating from i-1 down to 0.
Similarly, for the right side of the peak (k > i), the heights must be non-increasing, i.e., heights[k] <= heights[k-1]. We set heights[k] = min(maxHeights.get(k), heights[k-1]) and calculate the sum by iterating from i+1 up to n-1.
The sum for a given peak i is the sum of all these calculated heights. We keep track of the maximum sum found across all iterations.
import java.util.List; class Solution { public long maximumSumOfHeights(List<Integer> maxHeights) { int n = maxHeights.size(); long maxTotalSum = 0; for (int i = 0; i < n; i++) { long currentSum = maxHeights.get(i); // Calculate sum for the left side (non-decreasing) long prevHeight = maxHeights.get(i); for (int j = i - 1; j >= 0; j--) { long currentHeight = Math.min(maxHeights.get(j), prevHeight); currentSum += currentHeight; prevHeight = currentHeight; } // Calculate sum for the right side (non-increasing) prevHeight = maxHeights.get(i); for (int k = i + 1; k < n; k++) { long currentHeight = Math.min(maxHeights.get(k), prevHeight); currentSum += currentHeight; prevHeight = currentHeight; } maxTotalSum = Math.max(maxTotalSum, currentSum); } return maxTotalSum; }}Complexity
Time
O(N^2), where N is the number of towers. The outer loop runs N times for each potential peak, and the inner loops for calculating left and right sums take O(N) time in total for each peak.
Space
O(1) extra space, as we only use a few variables to store the current sums and heights during calculation.
Trade-offs
Pros
Simple to understand and implement.
It correctly solves the problem for smaller input sizes.
Cons
This approach is too slow for the given constraints (
nup to 10^5) and will likely result in a Time Limit Exceeded (TLE) error.
Solutions
Solution
class Solution {public long maximumSumOfHeights(List<Integer> maxHeights) { int n = maxHeights.size(); Deque<Integer> stk = new ArrayDeque<>(); int[] left = new int[n]; int[] right = new int[n]; Arrays.fill(left, -1); Arrays.fill(right, n); for (int i = 0; i < n; ++i) { int x = maxHeights.get(i); while (!stk.isEmpty() && maxHeights.get(stk.peek()) > x) { stk.pop(); } if (!stk.isEmpty()) { left[i] = stk.peek(); } stk.push(i); } stk.clear(); for (int i = n - 1; i >= 0; --i) { int x = maxHeights.get(i); while (!stk.isEmpty() && maxHeights.get(stk.peek()) >= x) { stk.pop(); } if (!stk.isEmpty()) { right[i] = stk.peek(); } stk.push(i); } long[] f = new long[n]; long[] g = new long[n]; for (int i = 0; i < n; ++i) { int x = maxHeights.get(i); if (i > 0 && x >= maxHeights.get(i - 1)) { f[i] = f[i - 1] + x; } else { int j = left[i]; f[i] = 1L * x * (i - j) + (j >= 0 ? f[j] : 0); } } for (int i = n - 1; i >= 0; --i) { int x = maxHeights.get(i); if (i < n - 1 && x >= maxHeights.get(i + 1)) { g[i] = g[i + 1] + x; } else { int j = right[i]; g[i] = 1L * x * (j - i) + (j < n ? g[j] : 0); } } long ans = 0; for (int i = 0; i < n; ++i) { ans = Math.max(ans, f[i] + g[i] - maxHeights.get(i)); } return ans; }}class Solution {public: long long maximumSumOfHeights(vector<int> &maxHeights) { int n = maxHeights.size(); stack<int> stk; vector<int> left(n, -1); vector<int> right(n, n); for (int i = 0; i < n; ++i) { int x = maxHeights[i]; while (!stk.empty() && maxHeights[stk.top()] > x) { stk.pop(); } if (!stk.empty()) { left[i] = stk.top(); } stk.push(i); } stk = stack<int>(); for (int i = n - 1; ~i; --i) { int x = maxHeights[i]; while (!stk.empty() && maxHeights[stk.top()] >= x) { stk.pop(); } if (!stk.empty()) { right[i] = stk.top(); } stk.push(i); } long long f[n], g[n]; for (int i = 0; i < n; ++i) { int x = maxHeights[i]; if (i && x >= maxHeights[i - 1]) { f[i] = f[i - 1] + x; } else { int j = left[i]; f[i] = 1LL * x * (i - j) + (j != -1 ? f[j] : 0); } } for (int i = n - 1; ~i; --i) { int x = maxHeights[i]; if (i < n - 1 && x >= maxHeights[i + 1]) { g[i] = g[i + 1] + x; } else { int j = right[i]; g[i] = 1LL * x * (j - i) + (j != n ? g[j] : 0); } } long long ans = 0; for (int i = 0; i < n; ++i) { ans = max(ans, f[i] + g[i] - maxHeights[i]); } return ans; }};class Solution: def maximumSumOfHeights(self, maxHeights: List[int]) -> int: n = len(maxHeights) stk = [] left = [- 1] * n for i, x in enumerate(maxHeights): while stk and maxHeights[stk[- 1]] > x: stk . pop() if stk: left[i] = stk[- 1] stk . append(i) stk = [] right = [n] * n for i in range(n - 1, - 1, - 1): x = maxHeights[i] while stk and maxHeights[stk[- 1]] >= x: stk . pop() if stk: right[i] = stk[- 1] stk . append(i) f = [0] * n for i, x in enumerate(maxHeights): if i and x >= maxHeights[i - 1]: f[i] = f[i - 1] + x else: j = left[i] f[i] = x * (i - j) + (f[j] if j != - 1 else 0) g = [0] * n for i in range(n - 1, - 1, - 1): if i < n - 1 and maxHeights[i] >= maxHeights[i + 1]: g[i] = g[i + 1] + maxHeights[i] else: j = right[i] g[i] = maxHeights[i] * (j - i) + (g[j] if j != n else 0) return max(a + b - c for a, b, c in zip(f, g, maxHeights))Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.