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:
- Each tower's height must be a positive integer (greater than 0) and cannot exceed its corresponding
maximumHeight[i]
value - 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.
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.
Solution Approach
The implementation follows a sorting and greedy strategy:
-
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. -
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. -
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. -
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
- Calculate the actual height to assign:
-
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 EvaluatorExample 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
In a binary min heap, the minimum element can be found in:
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
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!