Facebook Pixel

2866. Beautiful Towers II

Problem Description

You are given a 0-indexed array maxHeights of n integers, where each element represents the maximum allowed height for a tower at that position.

Your task is to build n towers along a coordinate line, where the i-th tower is built at coordinate i with height heights[i]. You need to find a configuration that maximizes the total sum of all tower heights.

The configuration must satisfy two conditions to be considered beautiful:

  1. Each tower's height must be between 1 and its maximum allowed height: 1 <= heights[i] <= maxHeights[i]

  2. The heights must form a mountain array, which means:

    • There exists a peak index i where:
      • All towers before the peak are non-decreasing: heights[j-1] <= heights[j] for all 0 < j <= i
      • All towers after the peak are non-increasing: heights[k+1] <= heights[k] for all i <= k < n-1

In other words, the heights should increase (or stay the same) up to some peak position, then decrease (or stay the same) from that peak to the end. The peak can be at any position, including the first or last tower.

The goal is to find the maximum possible sum of all tower heights in a beautiful configuration.

For example, if maxHeights = [5, 3, 4, 1, 1], one possible beautiful configuration could have heights [3, 3, 4, 1, 1] with peak at index 2, giving a sum of 12. The algorithm needs to find the optimal configuration that maximizes this sum while respecting all constraints.

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

Intuition

The key insight is that in a mountain array, every position could potentially be the peak. So we need to consider each position as a possible peak and find the maximum sum achievable.

For any position i chosen as the peak, we need to:

  • Build an increasing (or non-decreasing) sequence from left to position i
  • Build a decreasing (or non-increasing) sequence from position i to the right

The challenge is that when building these sequences, each tower's height is constrained by its maxHeights value. When we encounter a position with a smaller maxHeights value, it acts as a bottleneck - all subsequent towers in that direction must be at most that height.

This leads us to think about the problem in two parts:

  • f[i]: The maximum sum we can achieve from the left side up to position i, where position i is treated as the peak from the left perspective
  • g[i]: The maximum sum we can achieve from position i to the right side, where position i is treated as the peak from the right perspective

For computing f[i], when we're at position i:

  • If maxHeights[i] >= maxHeights[i-1], we can simply extend the previous configuration by adding maxHeights[i]
  • If maxHeights[i] < maxHeights[i-1], then maxHeights[i] becomes a bottleneck. We need to find all previous positions that would be affected by this constraint

The bottleneck effect means that when we have a smaller maxHeights[i], all towers between the last position with maxHeights <= maxHeights[i] and position i must be capped at maxHeights[i]. This is where the monotonic stack comes in - it helps us efficiently find these bottleneck positions.

Similarly, we compute g[i] by processing from right to left.

The final answer for each potential peak i would be f[i] + g[i] - maxHeights[i] (we subtract maxHeights[i] because it's counted twice - once in f[i] and once in g[i]). We take the maximum across all positions to get our answer.

Learn more about Stack and Monotonic Stack patterns.

Solution Approach

The solution uses dynamic programming combined with monotonic stacks to efficiently compute the maximum sum for each possible peak position.

Step 1: Build the left and right arrays using monotonic stacks

First, we build two auxiliary arrays:

  • left[i]: The index of the nearest tower to the left with maxHeights <= maxHeights[i]
  • right[i]: The index of the nearest tower to the right with maxHeights < maxHeights[i]

For the left array:

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)

We maintain a monotonic increasing stack. When we encounter a smaller value, we pop elements until we find one that's less than or equal to the current value.

Similarly for the right array, but we traverse from right to left:

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)

Step 2: Compute f[i] - the maximum sum from left to position i

For each position i, we calculate the maximum sum if we build towers from the left up to position 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)

The state transition follows:

  • If maxHeights[i] >= maxHeights[i-1]: We can extend the previous configuration, so f[i] = f[i-1] + maxHeights[i]
  • Otherwise: maxHeights[i] becomes a bottleneck. All towers from position j+1 to i must have height maxHeights[i], where j = left[i]. So f[i] = maxHeights[i] * (i - j) + f[j]

Step 3: Compute g[i] - the maximum sum from position i to the right

Similarly, we compute from right to left:

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)

Step 4: Find the maximum sum

For each position i as the peak, the total sum would be f[i] + g[i] - maxHeights[i]. We subtract maxHeights[i] because it's counted in both f[i] and g[i].

return max(a + b - c for a, b, c in zip(f, g, maxHeights))

The time complexity is O(n) since each element is pushed and popped from the stack at most once, and we iterate through the array a constant number of times. The space complexity is O(n) for the auxiliary 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 maxHeights = [5, 3, 4, 1, 1].

Step 1: Build left and right arrays using monotonic stacks

Building left array (nearest left element with maxHeights <= current):

  • i=0, x=5: stack empty, left[0] = -1, stack = [0]
  • i=1, x=3: pop 0 (5>3), stack empty, left[1] = -1, stack = [1]
  • i=2, x=4: 3≤4, left[2] = 1, stack = [1, 2]
  • i=3, x=1: pop 2 (4>1), pop 1 (3>1), stack empty, left[3] = -1, stack = [3]
  • i=4, x=1: 1≤1, left[4] = 3, stack = [3, 4]

Result: left = [-1, -1, 1, -1, 3]

Building right array (nearest right element with maxHeights < current):

  • i=4, x=1: stack empty, right[4] = 5, stack = [4]
  • i=3, x=1: pop 4 (1≥1), stack empty, right[3] = 5, stack = [3]
  • i=2, x=4: 1<4, right[2] = 3, stack = [3, 2]
  • i=1, x=3: pop 2 (4≥3), 1<3, right[1] = 3, stack = [3, 1]
  • i=0, x=5: pop 1 (3<5), pop 3 (1<5), stack empty, right[0] = 5, stack = [0]

Result: right = [5, 3, 3, 5, 5]

Step 2: Compute f[i] - maximum sum from left to position i

  • f[0]: Base case, f[0] = 5
  • f[1]: 3 < 5, bottleneck at i=1. j=left[1]=-1, f[1] = 3×(1-(-1)) + 0 = 6
  • f[2]: 4 ≥ 3, extend previous, f[2] = f[1] + 4 = 6 + 4 = 10
  • f[3]: 1 < 4, bottleneck at i=3. j=left[3]=-1, f[3] = 1×(3-(-1)) + 0 = 4
  • f[4]: 1 ≥ 1, extend previous, f[4] = f[3] + 1 = 4 + 1 = 5

Result: f = [5, 6, 10, 4, 5]

Step 3: Compute g[i] - maximum sum from position i to the right

  • g[4]: Base case, g[4] = 1
  • g[3]: 1 ≥ 1, extend next, g[3] = g[4] + 1 = 1 + 1 = 2
  • g[2]: 4 > 1, bottleneck at i=2. j=right[2]=3, g[2] = 4×(3-2) + g[3] = 4 + 2 = 6
  • g[1]: 3 > 1, bottleneck at i=1. j=right[1]=3, g[1] = 3×(3-1) + g[3] = 6 + 2 = 8
  • g[0]: 5 > 3, bottleneck at i=0. j=right[0]=5, g[0] = 5×(5-0) + 0 = 25

Result: g = [25, 8, 6, 2, 1]

Step 4: Find maximum sum

For each position as peak:

  • Peak at i=0: f[0] + g[0] - maxHeights[0] = 5 + 25 - 5 = 25
  • Peak at i=1: f[1] + g[1] - maxHeights[1] = 6 + 8 - 3 = 11
  • Peak at i=2: f[2] + g[2] - maxHeights[2] = 10 + 6 - 4 = 12
  • Peak at i=3: f[3] + g[3] - maxHeights[3] = 4 + 2 - 1 = 5
  • Peak at i=4: f[4] + g[4] - maxHeights[4] = 5 + 1 - 1 = 5

Maximum = 25 (peak at position 0)

This gives us the configuration: heights = [5, 3, 3, 1, 1] with sum = 13.

Wait, let me recalculate g[0]:

  • g[0]: j=right[0]=5 (out of bounds), so g[0] = 5×(5-0) + 0 = 25

But this seems incorrect. Let me recalculate more carefully:

Actually, when peak is at i=0, we need heights to be non-increasing from position 0 to the right.

  • With peak at i=0, the best configuration would be [5, 3, 3, 1, 1], giving sum = 13
  • With peak at i=2, the best configuration would be [3, 3, 4, 1, 1], giving sum = 12

The maximum sum is 13 with peak at position 0.

Solution Implementation

1class Solution:
2    def maximumSumOfHeights(self, maxHeights: List[int]) -> int:
3        n = len(maxHeights)
4      
5        # Find the previous smaller element for each position
6        # left[i] stores the index of the previous element smaller than maxHeights[i]
7        stack = []
8        left_smaller = [-1] * n
9        for i, height in enumerate(maxHeights):
10            # Pop elements from stack that are greater than current height
11            while stack and maxHeights[stack[-1]] > height:
12                stack.pop()
13            # If stack has elements, top element is the previous smaller
14            if stack:
15                left_smaller[i] = stack[-1]
16            stack.append(i)
17      
18        # Find the next smaller or equal element for each position
19        # right[i] stores the index of the next element smaller or equal to maxHeights[i]
20        stack = []
21        right_smaller = [n] * n
22        for i in range(n - 1, -1, -1):
23            height = maxHeights[i]
24            # Pop elements from stack that are greater than or equal to current height
25            while stack and maxHeights[stack[-1]] >= height:
26                stack.pop()
27            # If stack has elements, top element is the next smaller or equal
28            if stack:
29                right_smaller[i] = stack[-1]
30            stack.append(i)
31      
32        # Calculate maximum sum considering each position as part of increasing sequence from left
33        # prefix_sum[i] = maximum sum from start to position i with non-decreasing constraint
34        prefix_sum = [0] * n
35        for i, height in enumerate(maxHeights):
36            if i > 0 and height >= maxHeights[i - 1]:
37                # Current height is greater than or equal to previous, extend the sequence
38                prefix_sum[i] = prefix_sum[i - 1] + height
39            else:
40                # Current height breaks the non-decreasing pattern
41                # All elements from left_smaller[i]+1 to i will have height maxHeights[i]
42                prev_smaller_idx = left_smaller[i]
43                elements_with_same_height = i - prev_smaller_idx
44                prefix_sum[i] = height * elements_with_same_height
45                if prev_smaller_idx != -1:
46                    prefix_sum[i] += prefix_sum[prev_smaller_idx]
47      
48        # Calculate maximum sum considering each position as part of decreasing sequence to right
49        # suffix_sum[i] = maximum sum from position i to end with non-increasing constraint
50        suffix_sum = [0] * n
51        for i in range(n - 1, -1, -1):
52            if i < n - 1 and maxHeights[i] >= maxHeights[i + 1]:
53                # Current height is greater than or equal to next, extend the sequence
54                suffix_sum[i] = suffix_sum[i + 1] + maxHeights[i]
55            else:
56                # Current height breaks the non-increasing pattern
57                # All elements from i to right_smaller[i]-1 will have height maxHeights[i]
58                next_smaller_idx = right_smaller[i]
59                elements_with_same_height = next_smaller_idx - i
60                suffix_sum[i] = maxHeights[i] * elements_with_same_height
61                if next_smaller_idx != n:
62                    suffix_sum[i] += suffix_sum[next_smaller_idx]
63      
64        # Find maximum sum by considering each position as the peak
65        # We subtract maxHeights[i] once since it's counted in both prefix and suffix
66        max_sum = 0
67        for i in range(n):
68            total = prefix_sum[i] + suffix_sum[i] - maxHeights[i]
69            max_sum = max(max_sum, total)
70      
71        return max_sum
72
1class Solution {
2    public long maximumSumOfHeights(List<Integer> maxHeights) {
3        int n = maxHeights.size();
4      
5        // Stack to maintain monotonic sequence
6        Deque<Integer> stack = new ArrayDeque<>();
7      
8        // left[i]: index of the nearest smaller element to the left of i (-1 if none)
9        int[] left = new int[n];
10        // right[i]: index of the nearest smaller or equal element to the right of i (n if none)
11        int[] right = new int[n];
12      
13        // Initialize arrays
14        Arrays.fill(left, -1);
15        Arrays.fill(right, n);
16      
17        // Find nearest smaller element to the left for each position
18        for (int i = 0; i < n; ++i) {
19            int currentHeight = maxHeights.get(i);
20          
21            // Pop elements from stack that are greater than current element
22            while (!stack.isEmpty() && maxHeights.get(stack.peek()) > currentHeight) {
23                stack.pop();
24            }
25          
26            // If stack is not empty, top element is the nearest smaller element to the left
27            if (!stack.isEmpty()) {
28                left[i] = stack.peek();
29            }
30          
31            stack.push(i);
32        }
33      
34        // Clear stack for reuse
35        stack.clear();
36      
37        // Find nearest smaller or equal element to the right for each position
38        for (int i = n - 1; i >= 0; --i) {
39            int currentHeight = maxHeights.get(i);
40          
41            // Pop elements from stack that are greater than or equal to current element
42            while (!stack.isEmpty() && maxHeights.get(stack.peek()) >= currentHeight) {
43                stack.pop();
44            }
45          
46            // If stack is not empty, top element is the nearest smaller element to the right
47            if (!stack.isEmpty()) {
48                right[i] = stack.peek();
49            }
50          
51            stack.push(i);
52        }
53      
54        // leftSum[i]: maximum sum of heights from index 0 to i with peak at i
55        long[] leftSum = new long[n];
56        // rightSum[i]: maximum sum of heights from index i to n-1 with peak at i
57        long[] rightSum = new long[n];
58      
59        // Calculate maximum sum for left side (including current position)
60        for (int i = 0; i < n; ++i) {
61            int currentHeight = maxHeights.get(i);
62          
63            if (i > 0 && currentHeight >= maxHeights.get(i - 1)) {
64                // Current height is greater than or equal to previous, can extend
65                leftSum[i] = leftSum[i - 1] + currentHeight;
66            } else {
67                // Current height is smaller, need to recalculate
68                int j = left[i];
69                // All elements from j+1 to i will have height equal to currentHeight
70                leftSum[i] = 1L * currentHeight * (i - j) + (j >= 0 ? leftSum[j] : 0);
71            }
72        }
73      
74        // Calculate maximum sum for right side (including current position)
75        for (int i = n - 1; i >= 0; --i) {
76            int currentHeight = maxHeights.get(i);
77          
78            if (i < n - 1 && currentHeight >= maxHeights.get(i + 1)) {
79                // Current height is greater than or equal to next, can extend
80                rightSum[i] = rightSum[i + 1] + currentHeight;
81            } else {
82                // Current height is smaller, need to recalculate
83                int j = right[i];
84                // All elements from i to j-1 will have height equal to currentHeight
85                rightSum[i] = 1L * currentHeight * (j - i) + (j < n ? rightSum[j] : 0);
86            }
87        }
88      
89        // Find maximum sum by trying each position as the peak
90        long maxSum = 0;
91        for (int i = 0; i < n; ++i) {
92            // Subtract maxHeights.get(i) once as it's counted in both leftSum and rightSum
93            maxSum = Math.max(maxSum, leftSum[i] + rightSum[i] - maxHeights.get(i));
94        }
95      
96        return maxSum;
97    }
98}
99
1class Solution {
2public:
3    long long maximumSumOfHeights(vector<int>& maxHeights) {
4        int n = maxHeights.size();
5      
6        // Find the previous smaller element for each position
7        stack<int> monotonic_stack;
8        vector<int> prev_smaller(n, -1);  // Index of previous element with smaller height
9        vector<int> next_smaller(n, n);   // Index of next element with smaller or equal height
10      
11        // Build prev_smaller array using monotonic stack
12        // For each position i, find the rightmost position j < i where maxHeights[j] < maxHeights[i]
13        for (int i = 0; i < n; ++i) {
14            int current_height = maxHeights[i];
15          
16            // Pop elements greater than current height
17            while (!monotonic_stack.empty() && maxHeights[monotonic_stack.top()] > current_height) {
18                monotonic_stack.pop();
19            }
20          
21            // The top of stack (if exists) is the previous smaller element
22            if (!monotonic_stack.empty()) {
23                prev_smaller[i] = monotonic_stack.top();
24            }
25          
26            monotonic_stack.push(i);
27        }
28      
29        // Clear the stack for reuse
30        monotonic_stack = stack<int>();
31      
32        // Build next_smaller array using monotonic stack (traverse from right to left)
33        // For each position i, find the leftmost position j > i where maxHeights[j] <= maxHeights[i]
34        for (int i = n - 1; i >= 0; --i) {
35            int current_height = maxHeights[i];
36          
37            // Pop elements greater than or equal to current height
38            while (!monotonic_stack.empty() && maxHeights[monotonic_stack.top()] >= current_height) {
39                monotonic_stack.pop();
40            }
41          
42            // The top of stack (if exists) is the next smaller element
43            if (!monotonic_stack.empty()) {
44                next_smaller[i] = monotonic_stack.top();
45            }
46          
47            monotonic_stack.push(i);
48        }
49      
50        // Calculate prefix sum assuming position i is the peak (non-increasing to the left)
51        long long prefix_sum[n];
52      
53        for (int i = 0; i < n; ++i) {
54            int current_height = maxHeights[i];
55          
56            // If current height is greater than or equal to previous, add to previous sum
57            if (i > 0 && current_height >= maxHeights[i - 1]) {
58                prefix_sum[i] = prefix_sum[i - 1] + current_height;
59            } else {
60                // Otherwise, all elements from prev_smaller[i]+1 to i will have height maxHeights[i]
61                int left_bound = prev_smaller[i];
62                int num_elements = i - left_bound;
63                prefix_sum[i] = 1LL * current_height * num_elements;
64              
65                // Add the contribution from elements before left_bound
66                if (left_bound != -1) {
67                    prefix_sum[i] += prefix_sum[left_bound];
68                }
69            }
70        }
71      
72        // Calculate suffix sum assuming position i is the peak (non-increasing to the right)
73        long long suffix_sum[n];
74      
75        for (int i = n - 1; i >= 0; --i) {
76            int current_height = maxHeights[i];
77          
78            // If current height is greater than or equal to next, add to next sum
79            if (i < n - 1 && current_height >= maxHeights[i + 1]) {
80                suffix_sum[i] = suffix_sum[i + 1] + current_height;
81            } else {
82                // Otherwise, all elements from i to next_smaller[i]-1 will have height maxHeights[i]
83                int right_bound = next_smaller[i];
84                int num_elements = right_bound - i;
85                suffix_sum[i] = 1LL * current_height * num_elements;
86              
87                // Add the contribution from elements after right_bound
88                if (right_bound != n) {
89                    suffix_sum[i] += suffix_sum[right_bound];
90                }
91            }
92        }
93      
94        // Find the maximum sum by trying each position as the peak
95        long long max_sum = 0;
96        for (int i = 0; i < n; ++i) {
97            // Subtract maxHeights[i] once since it's counted in both prefix and suffix
98            long long total_sum = prefix_sum[i] + suffix_sum[i] - maxHeights[i];
99            max_sum = max(max_sum, total_sum);
100        }
101      
102        return max_sum;
103    }
104};
105
1/**
2 * Calculates the maximum sum of heights for a mountain-shaped array
3 * where each position has a maximum height constraint
4 * @param maxHeights - Array of maximum allowed heights at each position
5 * @returns Maximum possible sum of heights
6 */
7function maximumSumOfHeights(maxHeights: number[]): number {
8    const n: number = maxHeights.length;
9  
10    // Find the previous smaller element for each position
11    const stack: number[] = [];
12    const previousSmallerIndex: number[] = Array(n).fill(-1);
13    const nextSmallerIndex: number[] = Array(n).fill(n);
14  
15    // Build previousSmallerIndex array using monotonic stack
16    // For each element, find the rightmost element to its left that is smaller
17    for (let i = 0; i < n; i++) {
18        const currentHeight: number = maxHeights[i];
19      
20        // Pop elements from stack that are greater than current
21        while (stack.length > 0 && maxHeights[stack[stack.length - 1]] > currentHeight) {
22            stack.pop();
23        }
24      
25        // If stack is not empty, top element is the previous smaller element
26        if (stack.length > 0) {
27            previousSmallerIndex[i] = stack[stack.length - 1];
28        }
29      
30        stack.push(i);
31    }
32  
33    // Clear stack for reuse
34    stack.length = 0;
35  
36    // Build nextSmallerIndex array using monotonic stack
37    // For each element, find the leftmost element to its right that is smaller or equal
38    for (let i = n - 1; i >= 0; i--) {
39        const currentHeight: number = maxHeights[i];
40      
41        // Pop elements from stack that are greater than or equal to current
42        while (stack.length > 0 && maxHeights[stack[stack.length - 1]] >= currentHeight) {
43            stack.pop();
44        }
45      
46        // If stack is not empty, top element is the next smaller element
47        if (stack.length > 0) {
48            nextSmallerIndex[i] = stack[stack.length - 1];
49        }
50      
51        stack.push(i);
52    }
53  
54    // Calculate maximum sum from left side (non-decreasing sequence ending at i)
55    const leftSum: number[] = Array(n).fill(0);
56  
57    // Calculate maximum sum from right side (non-increasing sequence starting at i)
58    const rightSum: number[] = Array(n).fill(0);
59  
60    // Build leftSum array
61    for (let i = 0; i < n; i++) {
62        const currentHeight: number = maxHeights[i];
63      
64        // If current can extend the previous non-decreasing sequence
65        if (i > 0 && currentHeight >= maxHeights[i - 1]) {
66            leftSum[i] = leftSum[i - 1] + currentHeight;
67        } else {
68            // Start a new non-decreasing sequence from previousSmallerIndex[i] + 1 to i
69            const prevIndex: number = previousSmallerIndex[i];
70            const rangeLength: number = i - prevIndex;
71            leftSum[i] = currentHeight * rangeLength + (prevIndex >= 0 ? leftSum[prevIndex] : 0);
72        }
73    }
74  
75    // Build rightSum array
76    for (let i = n - 1; i >= 0; i--) {
77        const currentHeight: number = maxHeights[i];
78      
79        // If current can extend the next non-increasing sequence
80        if (i + 1 < n && currentHeight >= maxHeights[i + 1]) {
81            rightSum[i] = rightSum[i + 1] + currentHeight;
82        } else {
83            // Start a new non-increasing sequence from i to nextSmallerIndex[i] - 1
84            const nextIndex: number = nextSmallerIndex[i];
85            const rangeLength: number = nextIndex - i;
86            rightSum[i] = currentHeight * rangeLength + (nextIndex < n ? rightSum[nextIndex] : 0);
87        }
88    }
89  
90    // Find the maximum sum by choosing each position as the peak
91    let maxSum: number = 0;
92    for (let i = 0; i < n; i++) {
93        // Subtract maxHeights[i] once since it's counted in both leftSum and rightSum
94        const totalSum: number = leftSum[i] + rightSum[i] - maxHeights[i];
95        maxSum = Math.max(maxSum, totalSum);
96    }
97  
98    return maxSum;
99}
100

Time and Space Complexity

Time Complexity: O(n)

The algorithm consists of several sequential phases, each taking linear time:

  • First monotonic stack traversal to build the left array: O(n) - each element is pushed and popped at most once
  • Second monotonic stack traversal to build the right array: O(n) - each element is pushed and popped at most once
  • Computing the f array: O(n) - single pass through the array
  • Computing the g array: O(n) - single pass through the array in reverse
  • Finding the maximum using zip and list comprehension: O(n) - single pass through all arrays

Total time complexity: O(n) + O(n) + O(n) + O(n) + O(n) = O(n)

Space Complexity: O(n)

The algorithm uses the following auxiliary space:

  • left array: O(n) space
  • right array: O(n) space
  • f array: O(n) space
  • g array: O(n) space
  • Two stacks (stk): O(n) space in worst case (though reused)
  • The final list comprehension creates a temporary list: O(n) space

Total space complexity: O(n)

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

Common Pitfalls

1. Incorrect Monotonic Stack Conditions

Pitfall: Using the wrong comparison operators when building the monotonic stacks for left and right arrays. A common mistake is using inconsistent conditions:

  • For the left array: Using >= instead of > when popping from stack
  • For the right array: Using > instead of >= when popping from stack

Why it matters: The asymmetry is intentional. For the left side, we want the nearest element that is less than or equal to the current element (so we pop while stack top is strictly greater). For the right side, we want the nearest element that is strictly less than the current element (so we pop while stack top is greater than or equal).

Solution:

# Correct implementation for left array
while stack and maxHeights[stack[-1]] > height:  # Strictly greater
    stack.pop()

# Correct implementation for right array  
while stack and maxHeights[stack[-1]] >= height:  # Greater or equal
    stack.pop()

2. Off-by-One Errors in DP Transitions

Pitfall: Incorrectly calculating the number of elements when computing prefix and suffix sums, especially when dealing with boundary conditions.

Example mistake:

# Wrong: Off by one
elements_with_same_height = i - prev_smaller_idx - 1  # Incorrect!

# Correct:
elements_with_same_height = i - prev_smaller_idx

Why it happens: When prev_smaller_idx = -1, the range is from index 0 to i (inclusive), which is i - (-1) = i + 1 elements. The formula i - prev_smaller_idx correctly handles this.

3. Double-Counting the Peak Height

Pitfall: Forgetting to subtract maxHeights[i] when combining prefix_sum[i] and suffix_sum[i], leading to counting the peak height twice.

Wrong approach:

# Incorrect: Peak height counted twice
max_sum = max(max_sum, prefix_sum[i] + suffix_sum[i])

Correct approach:

# Correct: Subtract the peak height once
max_sum = max(max_sum, prefix_sum[i] + suffix_sum[i] - maxHeights[i])

4. Mishandling Boundary Cases in DP

Pitfall: Not properly handling cases where left_smaller[i] = -1 or right_smaller[i] = n in the DP transitions.

Solution: Always check boundary conditions:

# For prefix_sum calculation
if prev_smaller_idx != -1:
    prefix_sum[i] += prefix_sum[prev_smaller_idx]

# For suffix_sum calculation  
if next_smaller_idx != n:
    suffix_sum[i] += suffix_sum[next_smaller_idx]

5. Confusion Between Index and Height Values

Pitfall: Mixing up array indices with height values, especially when working with the monotonic stack.

Example mistake:

# Wrong: Comparing indices instead of heights
while stack and stack[-1] > height:  # Comparing index with height!
    stack.pop()

# Correct: Compare heights using indices
while stack and maxHeights[stack[-1]] > height:
    stack.pop()

Best Practice: Always store indices in the stack and use them to access the actual height values when needed. This maintains consistency and makes the code easier to debug.

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

Which of the tree traversal order can be used to obtain elements in a binary search tree in sorted order?


Recommended Readings

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

Load More