1840. Maximum Building Height
Problem Description
You need to build n
new buildings in a city, numbered from 1
to n
and arranged in a line. Each building must follow these rules:
- Every building must have a non-negative integer height
- Building 1 must have height 0
- Adjacent buildings can differ in height by at most 1 (the height can increase or decrease by at most 1 between any two consecutive buildings)
Additionally, some buildings have maximum height restrictions given in a 2D array restrictions
, where restrictions[i] = [id_i, maxHeight_i]
means building id_i
cannot exceed height maxHeight_i
.
Key guarantees:
- Each building appears at most once in the restrictions
- Building 1 is never in the restrictions (since it must be height 0)
Your task is to find the maximum possible height of the tallest building while satisfying all the constraints.
For example, if you have 5 buildings and no restrictions, starting from building 1 with height 0, you could have heights like [0, 1, 2, 3, 4]
making the tallest building have height 4. But if there's a restriction that building 3 can't exceed height 1, you'd need to adjust the heights accordingly, perhaps to something like [0, 1, 1, 2, 3]
or [0, 1, 1, 0, 1]
, depending on what maximizes the tallest building's height.
The challenge is to figure out the optimal height assignment that respects both the adjacent height difference constraint and all the specific building restrictions while maximizing the peak height achieved.
Intuition
Think of the buildings as a mountain range where you can only climb or descend by 1 unit at a time. The restrictions act like "ceiling points" that your mountain cannot exceed at specific locations.
The key insight is that between any two restricted buildings, the height profile forms a "tent" shape - it goes up from the left restriction point and down to the right restriction point (or just up, or just down, depending on the distance and heights). To find the maximum possible height between two points, we need to know the exact height limits at those points first.
But here's the catch: the restrictions influence each other! A restriction at building 10 with max height 5 might actually need to be lower if there's a restriction at building 5 with max height 2 (since we can only increase by 1 per building). This creates a "propagation effect" where restrictions limit not just their own building but nearby buildings too.
So we process the restrictions in two passes:
- Left to right: Each restriction is limited by how high we can climb from the previous restriction. If building
i
is at heighth1
and buildingj
isd
positions away, we can climb at most toh1 + d
. - Right to left: Similarly, each restriction is limited by how high we can climb from the next restriction going backwards.
After both passes, we have the true maximum height possible at each restricted building.
Now, between any two consecutive restrictions, we want to build as high as possible. The optimal strategy is to climb as high as we can from both ends and meet at a peak in the middle. If we have heights h1
and h2
at positions p1
and p2
, and we want to reach maximum height t
somewhere between them, we need:
t - h1
steps to climb from the leftt - h2
steps to descend to the right- Total steps available:
p2 - p1
This gives us: (t - h1) + (t - h2) ≤ p2 - p1
, which solves to t ≤ (h1 + h2 + p2 - p1) / 2
.
The maximum height of all buildings is the maximum of all these peaks between consecutive restrictions.
Solution Approach
The implementation follows these key steps:
1. Prepare the restrictions list:
- Add
[1, 0]
to the restrictions (building 1 must have height 0) - Sort all restrictions by building number
- If the last building
n
isn't restricted, add[n, n-1]
as its maximum possible height (climbing 1 per building from building 1)
2. Left-to-right pass to propagate constraints:
for i in range(1, m):
r[i][1] = min(r[i][1], r[i-1][1] + r[i][0] - r[i-1][0])
For each restriction, we update its maximum height based on the previous restriction. The maximum height we can reach from building r[i-1][0]
with height r[i-1][1]
to building r[i][0]
is r[i-1][1] + (r[i][0] - r[i-1][0])
since we can increase by at most 1 per building. We take the minimum with the original restriction.
3. Right-to-left pass to propagate constraints:
for i in range(m - 2, 0, -1):
r[i][1] = min(r[i][1], r[i+1][1] + r[i+1][0] - r[i][0])
Similar logic but going backwards. Each restriction is limited by what we can reach from the next restriction when going in reverse.
4. Calculate maximum height between consecutive restrictions:
for i in range(m - 1):
t = (r[i][1] + r[i+1][1] + r[i+1][0] - r[i][0]) // 2
ans = max(ans, t)
Between any two consecutive restrictions at positions r[i][0]
and r[i+1][0]
with heights r[i][1]
and r[i+1][1]
, we calculate the maximum achievable height using the formula:
t = (r[i][1] + r[i+1][1] + r[i+1][0] - r[i][0]) / 2
This formula comes from the fact that to reach height t
from both ends:
- We need
t - r[i][1]
steps climbing from the left - We need
t - r[i+1][1]
steps descending to the right - Total distance available:
r[i+1][0] - r[i][0]
The constraint (t - r[i][1]) + (t - r[i+1][1]) ≤ r[i+1][0] - r[i][0]
gives us the maximum t
.
5. Return the maximum height found: The answer is the maximum height achievable across all segments between consecutive restrictions.
The time complexity is O(k log k)
where k
is the number of restrictions (for sorting), and space complexity is O(k)
for storing the modified restrictions list.
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 an example with n = 6
buildings and restrictions [[3, 2], [5, 2]]
.
Step 1: Prepare restrictions
- Add building 1 with height 0:
[[1, 0], [3, 2], [5, 2]]
- Already sorted by building number
- Add building 6 (last building):
[[1, 0], [3, 2], [5, 2], [6, 5]]
- Building 6 can be at most height 5 (distance 5 from building 1)
Step 2: Left-to-right pass
- Building 3: Can we reach height 2 from building 1?
- Distance: 3 - 1 = 2 steps
- Max height from building 1: 0 + 2 = 2
- min(2, 2) = 2 ✓ (no change needed)
- Building 5: Can we reach height 2 from building 3?
- Distance: 5 - 3 = 2 steps
- Max height from building 3: 2 + 2 = 4
- min(2, 4) = 2 ✓ (no change needed)
- Building 6: Can we reach height 5 from building 5?
- Distance: 6 - 5 = 1 step
- Max height from building 5: 2 + 1 = 3
- min(5, 3) = 3 (reduced from 5 to 3!)
Restrictions after left-to-right: [[1, 0], [3, 2], [5, 2], [6, 3]]
Step 3: Right-to-left pass
- Building 5: Can we reach height 2 from building 6?
- Distance: 6 - 5 = 1 step
- Max height from building 6: 3 + 1 = 4
- min(2, 4) = 2 ✓ (no change needed)
- Building 3: Can we reach height 2 from building 5?
- Distance: 5 - 3 = 2 steps
- Max height from building 5: 2 + 2 = 4
- min(2, 4) = 2 ✓ (no change needed)
- Building 1: Already fixed at 0
Final restrictions: [[1, 0], [3, 2], [5, 2], [6, 3]]
Step 4: Calculate maximum heights between consecutive restrictions
Between buildings 1 and 3:
- Heights: h₁ = 0, h₂ = 2
- Distance: 3 - 1 = 2
- Max height: (0 + 2 + 2) / 2 = 2
- This peak occurs at building 3 itself
Between buildings 3 and 5:
- Heights: h₁ = 2, h₂ = 2
- Distance: 5 - 3 = 2
- Max height: (2 + 2 + 2) / 2 = 3
- This peak occurs at building 4
Between buildings 5 and 6:
- Heights: h₁ = 2, h₂ = 3
- Distance: 6 - 5 = 1
- Max height: (2 + 3 + 1) / 2 = 3
- This peak occurs at building 6 itself
Step 5: Return maximum The maximum height is 3, achieved at building 4 and building 6.
The final building heights could be: [0, 1, 2, 3, 2, 3]
- Building 1: height 0 (required)
- Building 2: height 1 (climbing from 0)
- Building 3: height 2 (restricted, climbing from 1)
- Building 4: height 3 (peak between restrictions)
- Building 5: height 2 (restricted, descending from 3)
- Building 6: height 3 (climbing from 2)
Solution Implementation
1class Solution:
2 def maxBuilding(self, n: int, restrictions: List[List[int]]) -> int:
3 # Create a mutable copy of restrictions to avoid modifying the input
4 height_restrictions = restrictions.copy()
5
6 # Add the starting position: building 1 must have height 0
7 height_restrictions.append([1, 0])
8
9 # Sort restrictions by position
10 height_restrictions.sort()
11
12 # Add the last position if not already present
13 # The maximum possible height at position n is n-1 (starting from 0 at position 1)
14 if not height_restrictions or height_restrictions[-1][0] != n:
15 height_restrictions.append([n, n - 1])
16
17 num_restrictions = len(height_restrictions)
18
19 # Forward pass: propagate height constraints from left to right
20 # Each building's height is limited by the previous building's height plus the distance
21 for i in range(1, num_restrictions):
22 max_height_from_left = height_restrictions[i - 1][1] + (height_restrictions[i][0] - height_restrictions[i - 1][0])
23 height_restrictions[i][1] = min(height_restrictions[i][1], max_height_from_left)
24
25 # Backward pass: propagate height constraints from right to left
26 # Each building's height is also limited by the next building's height plus the distance
27 for i in range(num_restrictions - 2, 0, -1):
28 max_height_from_right = height_restrictions[i + 1][1] + (height_restrictions[i + 1][0] - height_restrictions[i][0])
29 height_restrictions[i][1] = min(height_restrictions[i][1], max_height_from_right)
30
31 # Find the maximum possible height between consecutive restriction points
32 max_height = 0
33 for i in range(num_restrictions - 1):
34 # Calculate the maximum height that can be achieved between two consecutive points
35 # The formula finds the peak height when growing from both endpoints
36 left_height = height_restrictions[i][1]
37 right_height = height_restrictions[i + 1][1]
38 distance = height_restrictions[i + 1][0] - height_restrictions[i][0]
39
40 # Maximum height between two points with given endpoint heights
41 peak_height = (left_height + right_height + distance) // 2
42 max_height = max(max_height, peak_height)
43
44 return max_height
45
1class Solution {
2 public int maxBuilding(int n, int[][] restrictions) {
3 // Create a list to store all restrictions including boundaries
4 List<int[]> restrictionList = new ArrayList<>();
5
6 // Add all given restrictions to the list
7 restrictionList.addAll(Arrays.asList(restrictions));
8
9 // Add the starting position (position 1 with height 0)
10 restrictionList.add(new int[] {1, 0});
11
12 // Sort restrictions by position (ascending order)
13 Collections.sort(restrictionList, (a, b) -> a[0] - b[0]);
14
15 // Add the ending position if not already present
16 // The maximum height at position n is n-1 (if no other restrictions)
17 if (restrictionList.get(restrictionList.size() - 1)[0] != n) {
18 restrictionList.add(new int[] {n, n - 1});
19 }
20
21 int totalRestrictions = restrictionList.size();
22
23 // Forward pass: propagate height constraints from left to right
24 // Each building's height is limited by the previous building's height + distance
25 for (int i = 1; i < totalRestrictions; ++i) {
26 int[] previousRestriction = restrictionList.get(i - 1);
27 int[] currentRestriction = restrictionList.get(i);
28 int distance = currentRestriction[0] - previousRestriction[0];
29 currentRestriction[1] = Math.min(currentRestriction[1],
30 previousRestriction[1] + distance);
31 }
32
33 // Backward pass: propagate height constraints from right to left
34 // Ensure consistency by checking constraints in reverse direction
35 for (int i = totalRestrictions - 2; i > 0; --i) {
36 int[] currentRestriction = restrictionList.get(i);
37 int[] nextRestriction = restrictionList.get(i + 1);
38 int distance = nextRestriction[0] - currentRestriction[0];
39 currentRestriction[1] = Math.min(currentRestriction[1],
40 nextRestriction[1] + distance);
41 }
42
43 // Calculate the maximum achievable height between consecutive restrictions
44 int maxHeight = 0;
45 for (int i = 0; i < totalRestrictions - 1; ++i) {
46 int[] leftRestriction = restrictionList.get(i);
47 int[] rightRestriction = restrictionList.get(i + 1);
48
49 // Calculate peak height between two restrictions
50 // Peak occurs at: (leftHeight + rightHeight + distance) / 2
51 int distance = rightRestriction[0] - leftRestriction[0];
52 int peakHeight = (leftRestriction[1] + rightRestriction[1] + distance) / 2;
53 maxHeight = Math.max(maxHeight, peakHeight);
54 }
55
56 return maxHeight;
57 }
58}
59
1class Solution {
2public:
3 int maxBuilding(int n, vector<vector<int>>& restrictions) {
4 // Add the starting position (building 1 with height 0)
5 restrictions.push_back({1, 0});
6
7 // Sort restrictions by position
8 ranges::sort(restrictions);
9
10 // Add the last building if not already restricted
11 if (restrictions.back()[0] != n) {
12 restrictions.push_back({n, n - 1});
13 }
14
15 int restrictionCount = restrictions.size();
16
17 // Forward pass: propagate height restrictions from left to right
18 // Each building's height is limited by the previous building's height plus the distance
19 for (int i = 1; i < restrictionCount; ++i) {
20 int currentPos = restrictions[i][0];
21 int prevPos = restrictions[i - 1][0];
22 int distance = currentPos - prevPos;
23 int maxHeightFromPrev = restrictions[i - 1][1] + distance;
24
25 restrictions[i][1] = min(restrictions[i][1], maxHeightFromPrev);
26 }
27
28 // Backward pass: propagate height restrictions from right to left
29 // Each building's height is also limited by the next building's height plus the distance
30 for (int i = restrictionCount - 2; i > 0; --i) {
31 int currentPos = restrictions[i][0];
32 int nextPos = restrictions[i + 1][0];
33 int distance = nextPos - currentPos;
34 int maxHeightFromNext = restrictions[i + 1][1] + distance;
35
36 restrictions[i][1] = min(restrictions[i][1], maxHeightFromNext);
37 }
38
39 // Calculate the maximum height achievable between consecutive restrictions
40 int maxHeight = 0;
41 for (int i = 0; i < restrictionCount - 1; ++i) {
42 int leftPos = restrictions[i][0];
43 int rightPos = restrictions[i + 1][0];
44 int leftHeight = restrictions[i][1];
45 int rightHeight = restrictions[i + 1][1];
46 int distance = rightPos - leftPos;
47
48 // The peak height between two restrictions occurs at the midpoint
49 // Formula: (leftHeight + rightHeight + distance) / 2
50 int peakHeight = (leftHeight + rightHeight + distance) / 2;
51 maxHeight = max(maxHeight, peakHeight);
52 }
53
54 return maxHeight;
55 }
56};
57
1/**
2 * Finds the maximum height of the tallest building that can be built
3 * given position restrictions
4 * @param n - Total number of positions (1 to n)
5 * @param restrictions - Array of [position, maxHeight] restrictions
6 * @returns Maximum achievable building height
7 */
8function maxBuilding(n: number, restrictions: number[][]): number {
9 // Add restriction for position 1 (must start at height 0)
10 restrictions.push([1, 0]);
11
12 // Sort restrictions by position in ascending order
13 restrictions.sort((a, b) => a[0] - b[0]);
14
15 // Add restriction for the last position if not already present
16 // Maximum possible height at position n is n-1 (increasing by 1 each step from position 1)
17 if (restrictions[restrictions.length - 1][0] !== n) {
18 restrictions.push([n, n - 1]);
19 }
20
21 const totalRestrictions = restrictions.length;
22
23 // Forward pass: Propagate height constraints from left to right
24 // Each position's height is limited by the previous position's height plus the distance
25 for (let i = 1; i < totalRestrictions; ++i) {
26 const currentPosition = restrictions[i][0];
27 const previousPosition = restrictions[i - 1][0];
28 const previousMaxHeight = restrictions[i - 1][1];
29 const distanceBetweenPositions = currentPosition - previousPosition;
30
31 // Update current restriction based on what's reachable from the previous position
32 restrictions[i][1] = Math.min(
33 restrictions[i][1],
34 previousMaxHeight + distanceBetweenPositions
35 );
36 }
37
38 // Backward pass: Propagate height constraints from right to left
39 // Ensure consistency with constraints coming from both directions
40 for (let i = totalRestrictions - 2; i >= 0; --i) {
41 const currentPosition = restrictions[i][0];
42 const nextPosition = restrictions[i + 1][0];
43 const nextMaxHeight = restrictions[i + 1][1];
44 const distanceBetweenPositions = nextPosition - currentPosition;
45
46 // Update current restriction based on what's reachable from the next position
47 restrictions[i][1] = Math.min(
48 restrictions[i][1],
49 nextMaxHeight + distanceBetweenPositions
50 );
51 }
52
53 let maxHeight = 0;
54
55 // Calculate maximum achievable height between each pair of consecutive restrictions
56 for (let i = 0; i < totalRestrictions - 1; ++i) {
57 const leftPosition = restrictions[i][0];
58 const leftHeight = restrictions[i][1];
59 const rightPosition = restrictions[i + 1][0];
60 const rightHeight = restrictions[i + 1][1];
61 const distance = rightPosition - leftPosition;
62
63 // Maximum height achievable between two restricted positions
64 // occurs at the peak of a triangle formed by the two constraints
65 const peakHeight = Math.floor(
66 (leftHeight + rightHeight + distance) / 2
67 );
68
69 maxHeight = Math.max(maxHeight, peakHeight);
70 }
71
72 return maxHeight;
73}
74
Time and Space Complexity
The time complexity is O(m × log m)
, where m
is the number of restrictions (constraints).
Breaking down the time complexity:
- Adding restrictions to the list:
O(1)
for each append operation - Sorting the restrictions:
O(m × log m)
wherem
is the total number of restrictions including the added ones - First forward pass (propagating constraints left to right):
O(m)
iterations - Second backward pass (propagating constraints right to left):
O(m)
iterations - Final loop to calculate maximum height:
O(m)
iterations
The dominant operation is the sorting step, giving us an overall time complexity of O(m × log m)
.
The space complexity is O(m)
, where m
is the number of restrictions.
Space complexity analysis:
- The restrictions list
r
stores at mostm + 2
elements (original restrictions plus two boundary conditions) - No additional data structures are used that scale with input size
- Only constant extra variables are used (
ans
,t
, loop indices)
Therefore, the space complexity is O(m)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Modifying the Original Input Array
A critical pitfall is directly modifying the input restrictions
array when adding new constraints. This can cause issues if the function is called multiple times or if the caller expects the input to remain unchanged.
Problem Code:
def maxBuilding(self, n: int, restrictions: List[List[int]]) -> int:
restrictions.append([1, 0]) # Directly modifying input!
restrictions.sort()
# ... rest of code
Solution: Always create a copy of the input array before modifying it:
def maxBuilding(self, n: int, restrictions: List[List[int]]) -> int:
height_restrictions = restrictions.copy() # Create a copy first
height_restrictions.append([1, 0])
height_restrictions.sort()
# ... rest of code
2. Integer Division vs Float Division
When calculating the peak height between two restrictions, using float division can lead to incorrect results since building heights must be integers.
Problem Code:
peak_height = (left_height + right_height + distance) / 2 # Float division!
max_height = max(max_height, peak_height) # Comparing int with float
Solution: Use integer division to ensure the result is an integer:
peak_height = (left_height + right_height + distance) // 2 # Integer division
max_height = max(max_height, peak_height)
3. Forgetting to Check if Last Building Already Has a Restriction
Adding a restriction for building n
without checking if it already exists in the restrictions can create duplicate entries and incorrect calculations.
Problem Code:
height_restrictions.append([n, n - 1]) # Always adding, even if n is already restricted!
Solution: Check if the last building is already in the sorted restrictions before adding:
if not height_restrictions or height_restrictions[-1][0] != n: height_restrictions.append([n, n - 1])
4. Incorrect Backward Pass Range
The backward pass should skip index 0 (building 1 with height 0) since it's fixed and shouldn't be updated.
Problem Code:
for i in range(num_restrictions - 2, -1, -1): # Goes all the way to index 0!
height_restrictions[i][1] = min(height_restrictions[i][1], ...)
Solution: Stop at index 1 to avoid updating the fixed building 1:
for i in range(num_restrictions - 2, 0, -1): # Stops at index 1
height_restrictions[i][1] = min(height_restrictions[i][1], ...)
5. Not Handling Edge Case of Empty Restrictions
If the original restrictions list is empty, the code might fail when checking height_restrictions[-1][0]
.
Problem Code:
if height_restrictions[-1][0] != n: # IndexError if height_restrictions is empty! height_restrictions.append([n, n - 1])
Solution: Add a check for empty list:
if not height_restrictions or height_restrictions[-1][0] != n: height_restrictions.append([n, n - 1])
You are given an array of intervals where intervals[i] = [start_i, end_i]
represent the start and end of the ith
interval. You need to merge all overlapping intervals and return an array of the non-overlapping intervals that cover all the intervals in the input.
Recommended Readings
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
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!