Facebook Pixel

3301. Maximize the Total Height of Unique Towers

Problem Description

You are given an array maximumHeight where each element maximumHeight[i] represents the maximum allowed height for the i-th tower.

Your task is to assign a positive integer height to each tower following these rules:

  1. Each tower's height must be a positive integer (greater than 0) and cannot exceed its corresponding maximumHeight[i] value
  2. All towers must have unique heights (no two towers can have the same height)

The goal is to maximize the total sum of all tower heights. If it's impossible to assign valid heights to all towers while satisfying both conditions, return -1.

For example, if maximumHeight = [2, 3, 4], you could assign heights [2, 3, 4] or [1, 2, 4] or [1, 3, 4], etc. The maximum sum would be 2 + 3 + 4 = 9.

The solution uses a greedy approach by sorting the towers by their maximum allowed heights and assigning the highest possible valid height to each tower. Starting from the tower with the highest maximum allowed height, it assigns heights in descending order, ensuring each subsequent tower gets a height at least 1 less than the previous one. This guarantees uniqueness while maximizing the sum.

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

Intuition

To maximize the total sum, we want to assign the highest possible heights to towers. The key insight is that if we have multiple towers, we should prioritize giving higher heights to towers that have higher maximum limits.

Think about it this way: if we have two towers with maximum heights [5, 10], we want to assign height 10 to the second tower and height 5 (or less) to the first tower. If we did it the other way around (giving 5 to the second tower and something less to the first), we'd get a smaller sum.

This naturally leads us to sort the towers by their maximum heights. But here's the clever part: we process them from highest to lowest. Why? Because when we assign heights in descending order, we can ensure uniqueness while maximizing each tower's contribution.

Starting from the tower with the highest maximum limit, we assign it the highest possible height. For the next tower, we need to ensure uniqueness, so we assign it at most previous_height - 1. This creates a descending sequence of heights.

The variable mx tracks the maximum height we can assign to the current tower (considering the uniqueness constraint from previously assigned towers). For each tower, we take the minimum of its maximum allowed height and mx - 1. This ensures we respect both the tower's limit and the uniqueness requirement.

If at any point we need to assign a height of 0 or less (which happens when we have too many towers with low maximum heights), it becomes impossible to satisfy all constraints, so we return -1.

This greedy strategy works because assigning the maximum possible height at each step (while maintaining uniqueness) guarantees the optimal sum - there's no benefit to "saving" height capacity for later towers.

Learn more about Greedy and Sorting patterns.

Solution Approach

The implementation follows a sorting and greedy strategy:

  1. Sort the array in ascending order: We start by sorting maximumHeight array. This allows us to process towers from those with smaller maximum limits to those with larger limits when we iterate in reverse.

  2. Process towers from highest to lowest: We iterate through the sorted array in reverse order using maximumHeight[::-1]. This ensures we handle towers with higher maximum limits first, allowing us to assign them the highest possible heights.

  3. Track the maximum assignable height: We initialize a variable mx to infinity (inf) to track the maximum height we can assign to the current tower. This variable ensures we maintain the uniqueness constraint - each tower must have a height at least 1 less than the previous tower's height.

  4. Assign heights greedily: For each tower with maximum height x:

    • Calculate the actual height to assign: x = min(x, mx - 1). This takes the minimum of the tower's maximum allowed height and one less than the previously assigned height.
    • Check if assignment is valid: If x <= 0, it means we cannot assign a positive height to this tower while maintaining uniqueness, so we return -1.
    • Add the assigned height to the answer: ans += x
    • Update the maximum for the next tower: mx = x
  5. Return the total sum: After successfully assigning heights to all towers, return the accumulated sum ans.

The algorithm runs in O(n log n) time due to sorting, where n is the number of towers. The space complexity is O(1) if we don't count the space used for sorting.

Example walkthrough with maximumHeight = [2, 5, 3]:

  • After sorting: [2, 3, 5]
  • Process in reverse: [5, 3, 2]
  • Tower with max 5: assign height 5 (min(5, inf-1)), mx = 5, sum = 5
  • Tower with max 3: assign height 3 (min(3, 4)), mx = 3, sum = 8
  • Tower with max 2: assign height 2 (min(2, 2)), mx = 2, sum = 10
  • Return 10

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 maximumHeight = [2, 5, 3, 1]:

Step 1: Sort the array

  • Original: [2, 5, 3, 1]
  • After sorting: [1, 2, 3, 5]

Step 2: Process towers in reverse order We'll process [5, 3, 2, 1] and track our running sum and the maximum height we can assign next (mx).

Initial state:

  • ans = 0 (total sum)
  • mx = infinity (no restrictions yet)

Processing tower with max height 5:

  • Can assign: min(5, mx - 1) = min(5, inf - 1) = 5
  • Valid? Yes, 5 > 0 โœ“
  • Update: ans = 0 + 5 = 5, mx = 5
  • Heights assigned so far: [5]

Processing tower with max height 3:

  • Can assign: min(3, mx - 1) = min(3, 5 - 1) = min(3, 4) = 3
  • Valid? Yes, 3 > 0 โœ“
  • Update: ans = 5 + 3 = 8, mx = 3
  • Heights assigned so far: [5, 3]

Processing tower with max height 2:

  • Can assign: min(2, mx - 1) = min(2, 3 - 1) = min(2, 2) = 2
  • Valid? Yes, 2 > 0 โœ“
  • Update: ans = 8 + 2 = 10, mx = 2
  • Heights assigned so far: [5, 3, 2]

Processing tower with max height 1:

  • Can assign: min(1, mx - 1) = min(1, 2 - 1) = min(1, 1) = 1
  • Valid? Yes, 1 > 0 โœ“
  • Update: ans = 10 + 1 = 11, mx = 1
  • Heights assigned so far: [5, 3, 2, 1]

Result: Return 11

The final assignment gives towers heights [1, 5, 3, 2] (mapping back to original order), which are all unique, positive, and within their maximum limits. The sum is maximized at 11.

Example of impossible case: If maximumHeight = [1, 1, 1]:

  • After sorting: [1, 1, 1]
  • Process first tower (max=1): assign height 1
  • Process second tower (max=1): need height โ‰ค 0 (since previous was 1)
  • Return -1 because we can't assign positive unique heights

Solution Implementation

1from typing import List
2from math import inf
3
4class Solution:
5    def maximumTotalSum(self, maximumHeight: List[int]) -> int:
6        # Sort the maximum heights in ascending order
7        maximumHeight.sort()
8      
9        # Initialize total sum and the maximum allowed height for the next building
10        total_sum = 0
11        max_allowed_height = inf
12      
13        # Process buildings from tallest to shortest (reverse order)
14        for max_height in maximumHeight[::-1]:
15            # Assign the current building the minimum of its maximum height 
16            # and one less than the previous building's height
17            assigned_height = min(max_height, max_allowed_height - 1)
18          
19            # If we can't assign a positive height, it's impossible
20            if assigned_height <= 0:
21                return -1
22          
23            # Add the assigned height to the total sum
24            total_sum += assigned_height
25          
26            # Update the maximum allowed height for the next building
27            max_allowed_height = assigned_height
28      
29        return total_sum
30
1class Solution {
2    public long maximumTotalSum(int[] maximumHeight) {
3        long totalSum = 0;
4        int currentMaxAllowed = 1 << 30;  // Initialize with a large number (2^30)
5      
6        // Sort the array in ascending order
7        Arrays.sort(maximumHeight);
8      
9        // Iterate from the largest element to the smallest
10        for (int i = maximumHeight.length - 1; i >= 0; i--) {
11            // Assign the minimum of current element's max height and (currentMaxAllowed - 1)
12            // This ensures each height is strictly less than the previous one
13            int assignedHeight = Math.min(maximumHeight[i], currentMaxAllowed - 1);
14          
15            // If assigned height becomes non-positive, it's impossible to assign valid heights
16            if (assignedHeight <= 0) {
17                return -1;
18            }
19          
20            // Add the assigned height to the total sum
21            totalSum += assignedHeight;
22          
23            // Update the max allowed height for the next iteration
24            currentMaxAllowed = assignedHeight;
25        }
26      
27        return totalSum;
28    }
29}
30
1class Solution {
2public:
3    long long maximumTotalSum(vector<int>& maximumHeight) {
4        // Sort the array in descending order to process from largest to smallest
5        ranges::sort(maximumHeight, greater<int>());
6      
7        // Initialize the total sum result
8        long long totalSum = 0;
9      
10        // Track the maximum allowed height for the next element
11        // Start with a very large value (2^30)
12        int maxAllowedHeight = 1 << 30;
13      
14        // Process each height constraint
15        for (int currentHeight : maximumHeight) {
16            // The actual height must be at most the minimum of:
17            // 1. The original maximum height constraint
18            // 2. One less than the previous assigned height (to ensure uniqueness)
19            currentHeight = min(currentHeight, maxAllowedHeight - 1);
20          
21            // If we can't assign a positive height, it's impossible
22            if (currentHeight <= 0) {
23                return -1;
24            }
25          
26            // Add the assigned height to the total sum
27            totalSum += currentHeight;
28          
29            // Update the maximum allowed height for the next element
30            maxAllowedHeight = currentHeight;
31        }
32      
33        return totalSum;
34    }
35};
36
1/**
2 * Calculates the maximum total sum of heights with constraints.
3 * Each height must be unique and cannot exceed its maximum allowed value.
4 * Heights are assigned in descending order to maximize the sum.
5 * 
6 * @param maximumHeight - Array of maximum allowed heights for each position
7 * @returns Maximum total sum if valid assignment exists, -1 otherwise
8 */
9function maximumTotalSum(maximumHeight: number[]): number {
10    // Sort the array in descending order to greedily assign largest possible heights
11    maximumHeight.sort((a: number, b: number) => b - a);
12  
13    let totalSum: number = 0;
14    let previousAssignedHeight: number = Infinity;
15  
16    // Iterate through each maximum height constraint
17    for (const maxHeightConstraint of maximumHeight) {
18        // Assign the largest valid height that is:
19        // 1. Not exceeding the maximum height constraint
20        // 2. Strictly less than the previous assigned height (to ensure uniqueness)
21        const assignedHeight: number = Math.min(maxHeightConstraint, previousAssignedHeight - 1);
22      
23        // If no positive height can be assigned, the configuration is invalid
24        if (assignedHeight <= 0) {
25            return -1;
26        }
27      
28        // Add the assigned height to the total sum
29        totalSum += assignedHeight;
30      
31        // Update the previous height for the next iteration
32        previousAssignedHeight = assignedHeight;
33    }
34  
35    return totalSum;
36}
37

Time and Space Complexity

The time complexity is O(n ร— log n), where n is the length of the array maximumHeight. This is dominated by the sorting operation maximumHeight.sort(), which uses a comparison-based sorting algorithm (typically Timsort in Python) that has O(n ร— log n) time complexity. The subsequent loop that iterates through the sorted array in reverse order takes O(n) time, but this is overshadowed by the sorting cost.

The space complexity is O(log n). While the sorting operation is performed in-place and doesn't require additional space proportional to the input size, the sorting algorithm itself uses O(log n) space for the recursion stack during the sorting process (in the case of Timsort, this is for the merge operations). The reverse iteration maximumHeight[::-1] creates a reversed view but doesn't create a new list in modern Python implementations, and the few variables used (ans, mx, x) only require O(1) additional space.

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

Common Pitfalls

Pitfall 1: Not Handling the Impossible Case Correctly

The Problem: A common mistake is forgetting to check if it's impossible to assign valid heights. This happens when we have too many towers with low maximum heights. For example, with maximumHeight = [1, 1, 1, 1], we need to assign 4 different positive heights, but the maximum any tower can be is 1, making it impossible.

Why It Happens: Developers might focus on the greedy assignment logic but overlook the validation that ensures all towers can receive positive, unique heights.

The Solution: Always check if the assigned height becomes zero or negative:

if assigned_height <= 0:
    return -1

Pitfall 2: Incorrect Order of Processing

The Problem: Processing towers in the wrong order (from smallest to largest maximum height) leads to suboptimal solutions. For example, with maximumHeight = [5, 2, 4], if we process in ascending order [2, 4, 5], we might assign heights [2, 3, 4] = 9, but the optimal is [1, 4, 5] = 10.

Why It Happens: The intuition might be to handle constrained towers first, but this actually limits our choices for towers with higher maximum heights.

The Solution: Always sort first, then process in descending order:

maximumHeight.sort()
for max_height in maximumHeight[::-1]:  # Process from largest to smallest

Pitfall 3: Off-by-One Error in Height Assignment

The Problem: Forgetting to subtract 1 when calculating the next available height, which would allow duplicate heights. Using max_allowed_height instead of max_allowed_height - 1 violates the uniqueness constraint.

Why It Happens: The uniqueness requirement is easy to overlook when focusing on maximizing the sum.

The Solution: Ensure each tower gets a height strictly less than the previous:

assigned_height = min(max_height, max_allowed_height - 1)

Pitfall 4: Integer Overflow with Initial Value

The Problem: Using a very large integer like 10^9 as the initial maximum might seem sufficient, but if the first tower has a maximum height larger than this value, it would be incorrectly capped.

Why It Happens: Hardcoding large values instead of using infinity can create edge cases.

The Solution: Use float('inf') or math.inf for the initial value:

max_allowed_height = inf  # Not 10**9 or other arbitrary large number
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

In a binary min heap, the minimum element can be found in:


Recommended Readings

Want a Structured Path to Master System Design Too? Donโ€™t Miss This!

Load More