807. Max Increase to Keep City Skyline
Problem Description
You have a city represented as an n x n
grid where each cell grid[r][c]
contains the height of a building at row r
and column c
.
The skyline is what you see when looking at the city from far away in each direction (north, east, south, west). When viewing from:
- North or South: You see the maximum height in each column
- East or West: You see the maximum height in each row
Your task is to increase the heights of buildings to maximize the total sum of increases, but with one constraint: the skyline from all four directions must remain unchanged.
For example, if a building at position (i, j)
has height grid[i][j]
, you can increase it up to min(rowMax[i], colMax[j])
where:
rowMax[i]
is the maximum height in rowi
(the skyline when viewing from east/west)colMax[j]
is the maximum height in columnj
(the skyline when viewing from north/south)
This ensures that:
- The building doesn't become taller than the tallest building in its row (preserving east/west skyline)
- The building doesn't become taller than the tallest building in its column (preserving north/south skyline)
Return the maximum total sum by which you can increase the heights of all buildings without changing any skyline view.
Intuition
The key insight is understanding what preserves the skyline. When we look at the city from any direction, we only see the tallest building in each row or column - these form the skyline silhouette.
Think about a single building at position (i, j)
. If we increase its height:
- It cannot exceed the current maximum in row
i
, otherwise we'd change the east/west skyline - It cannot exceed the current maximum in column
j
, otherwise we'd change the north/south skyline
So the maximum safe height for any building is the minimum of these two constraints: min(rowMax[i], colMax[j])
.
Why does this work? Consider what happens when we increase a building's height to exactly min(rowMax[i], colMax[j])
:
- If
rowMax[i] < colMax[j]
, the building reaches the row's maximum but stays below the column's maximum. The row skyline stays the same (still has the same max), and the column skyline is unaffected. - If
colMax[j] < rowMax[i]
, the building reaches the column's maximum but stays below the row's maximum. Similar reasoning applies. - If they're equal, the building reaches both limits simultaneously but doesn't exceed either.
This greedy approach is optimal because we're maximizing each building's height independently within its constraints. Since each building's contribution to the total sum is independent, maximizing each one individually gives us the maximum total sum.
The solution naturally follows: first scan the grid to find all row and column maximums, then for each building, calculate how much we can increase it: min(rowMax[i], colMax[j]) - grid[i][j]
.
Learn more about Greedy patterns.
Solution Approach
The implementation follows a straightforward greedy approach with two passes through the grid:
Step 1: Calculate Row and Column Maximums
First, we need to find the maximum height in each row and column:
row_max = [max(row) for row in grid]
- This iterates through each row and finds its maximum valuecol_max = [max(col) for col in zip(*grid)]
- This useszip(*grid)
to transpose the matrix (converting rows to columns), then finds the maximum of each column
The zip(*grid)
operation is a Python idiom that effectively transposes the matrix. For example, if grid = [[1,2], [3,4]]
, then zip(*grid)
gives us [(1,3), (2,4)]
, which are the columns.
Step 2: Calculate Total Increase
Next, we calculate the sum of all possible increases:
sum(
min(row_max[i], col_max[j]) - x
for i, row in enumerate(grid)
for j, x in enumerate(row)
)
This nested comprehension:
- Iterates through each cell
(i, j)
with valuex
in the grid - Calculates the maximum safe height:
min(row_max[i], col_max[j])
- Subtracts the current height
x
to get the increase amount - Sums all these increases
Time Complexity: O(nΒ²)
where n
is the grid dimension - we traverse the grid twice (once for finding maximums, once for calculating increases)
Space Complexity: O(n)
for storing the row_max
and col_max
arrays
The beauty of this solution is its simplicity - by pre-computing the constraints (row and column maximums), we can determine each building's optimal height in a single calculation.
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 a small example to illustrate the solution approach.
Consider this 3Γ3 grid:
grid = [[3, 0, 8], [2, 4, 5], [9, 2, 6]]
Step 1: Calculate Row and Column Maximums
First, find the maximum height in each row:
- Row 0: max(3, 0, 8) = 8
- Row 1: max(2, 4, 5) = 5
- Row 2: max(9, 2, 6) = 9
row_max = [8, 5, 9]
Next, find the maximum height in each column:
- Column 0: max(3, 2, 9) = 9
- Column 1: max(0, 4, 2) = 4
- Column 2: max(8, 5, 6) = 8
col_max = [9, 4, 8]
Step 2: Calculate Increases for Each Building
Now examine each building and determine how much we can increase it:
Position (0,0): Current height = 3
- Row max = 8, Column max = 9
- Can increase to min(8, 9) = 8
- Increase = 8 - 3 = 5
Position (0,1): Current height = 0
- Row max = 8, Column max = 4
- Can increase to min(8, 4) = 4
- Increase = 4 - 0 = 4
Position (0,2): Current height = 8
- Row max = 8, Column max = 8
- Can increase to min(8, 8) = 8
- Increase = 8 - 8 = 0 (already at maximum)
Position (1,0): Current height = 2
- Row max = 5, Column max = 9
- Can increase to min(5, 9) = 5
- Increase = 5 - 2 = 3
Position (1,1): Current height = 4
- Row max = 5, Column max = 4
- Can increase to min(5, 4) = 4
- Increase = 4 - 4 = 0 (already at maximum)
Position (1,2): Current height = 5
- Row max = 5, Column max = 8
- Can increase to min(5, 8) = 5
- Increase = 5 - 5 = 0 (already at maximum)
Position (2,0): Current height = 9
- Row max = 9, Column max = 9
- Can increase to min(9, 9) = 9
- Increase = 9 - 9 = 0 (already at maximum)
Position (2,1): Current height = 2
- Row max = 9, Column max = 4
- Can increase to min(9, 4) = 4
- Increase = 4 - 2 = 2
Position (2,2): Current height = 6
- Row max = 9, Column max = 8
- Can increase to min(9, 8) = 8
- Increase = 8 - 6 = 2
Total sum of increases = 5 + 4 + 0 + 3 + 0 + 0 + 0 + 2 + 2 = 16
The final grid after all increases would be:
[[8, 4, 8], [5, 4, 5], [9, 4, 8]]
Notice how the skylines remain unchanged:
- From North/South (column maxes): [9, 4, 8] β
- From East/West (row maxes): [8, 5, 9] β
Solution Implementation
1class Solution:
2 def maxIncreaseKeepingSkyline(self, grid: List[List[int]]) -> int:
3 # Find the maximum height in each row (skyline from the side)
4 row_maximums = [max(row) for row in grid]
5
6 # Find the maximum height in each column (skyline from the front/back)
7 # zip(*grid) transposes the grid to access columns as rows
8 column_maximums = [max(column) for column in zip(*grid)]
9
10 # Calculate the total increase possible
11 total_increase = 0
12
13 # Iterate through each cell in the grid
14 for row_index, row in enumerate(grid):
15 for column_index, current_height in enumerate(row):
16 # The maximum height for this cell is limited by both skylines
17 # We take the minimum of the row and column maximum heights
18 max_allowed_height = min(row_maximums[row_index], column_maximums[column_index])
19
20 # Add the difference between max allowed and current height
21 total_increase += max_allowed_height - current_height
22
23 return total_increase
24
1class Solution {
2 public int maxIncreaseKeepingSkyline(int[][] grid) {
3 // Get dimensions of the grid
4 int rows = grid.length;
5 int cols = grid[0].length;
6
7 // Arrays to store the maximum height in each row and column
8 int[] maxHeightInRow = new int[rows];
9 int[] maxHeightInCol = new int[cols];
10
11 // First pass: Find the maximum height in each row and column
12 for (int row = 0; row < rows; row++) {
13 for (int col = 0; col < cols; col++) {
14 // Update the maximum height for current row
15 maxHeightInRow[row] = Math.max(maxHeightInRow[row], grid[row][col]);
16 // Update the maximum height for current column
17 maxHeightInCol[col] = Math.max(maxHeightInCol[col], grid[row][col]);
18 }
19 }
20
21 // Calculate the total increase in height
22 int totalIncrease = 0;
23
24 // Second pass: Calculate how much each building can be increased
25 for (int row = 0; row < rows; row++) {
26 for (int col = 0; col < cols; col++) {
27 // The maximum allowed height is the minimum of row and column skylines
28 // to maintain both skyline views
29 int maxAllowedHeight = Math.min(maxHeightInRow[row], maxHeightInCol[col]);
30 // Add the possible increase for this building
31 totalIncrease += maxAllowedHeight - grid[row][col];
32 }
33 }
34
35 return totalIncrease;
36 }
37}
38
1class Solution {
2public:
3 int maxIncreaseKeepingSkyline(vector<vector<int>>& grid) {
4 // Get dimensions of the grid
5 int numRows = grid.size();
6 int numCols = grid[0].size();
7
8 // Store the maximum height in each row and column
9 vector<int> maxHeightInRow(numRows, 0);
10 vector<int> maxHeightInCol(numCols, 0);
11
12 // Find the maximum height for each row and column
13 for (int row = 0; row < numRows; ++row) {
14 for (int col = 0; col < numCols; ++col) {
15 maxHeightInRow[row] = max(maxHeightInRow[row], grid[row][col]);
16 maxHeightInCol[col] = max(maxHeightInCol[col], grid[row][col]);
17 }
18 }
19
20 // Calculate the total increase possible while maintaining skyline
21 int totalIncrease = 0;
22 for (int row = 0; row < numRows; ++row) {
23 for (int col = 0; col < numCols; ++col) {
24 // Each building can be increased up to the minimum of its row and column max
25 // to maintain the skyline from both views
26 int maxAllowedHeight = min(maxHeightInRow[row], maxHeightInCol[col]);
27 totalIncrease += maxAllowedHeight - grid[row][col];
28 }
29 }
30
31 return totalIncrease;
32 }
33};
34
1/**
2 * Calculates the maximum total sum that can be added to building heights
3 * while maintaining the skyline when viewed from all four directions.
4 *
5 * @param grid - 2D array representing building heights in a city grid
6 * @returns The maximum total sum that can be added to all buildings
7 */
8function maxIncreaseKeepingSkyline(grid: number[][]): number {
9 // Get grid dimensions
10 const rowCount: number = grid.length;
11 const columnCount: number = grid[0].length;
12
13 // Initialize arrays to store maximum height for each row and column
14 const maxHeightPerRow: number[] = Array(rowCount).fill(0);
15 const maxHeightPerColumn: number[] = Array(columnCount).fill(0);
16
17 // Find the maximum height in each row and column
18 // This represents the skyline when viewed from each direction
19 for (let row: number = 0; row < rowCount; ++row) {
20 for (let column: number = 0; column < columnCount; ++column) {
21 maxHeightPerRow[row] = Math.max(maxHeightPerRow[row], grid[row][column]);
22 maxHeightPerColumn[column] = Math.max(maxHeightPerColumn[column], grid[row][column]);
23 }
24 }
25
26 // Calculate the total increase possible
27 let totalIncrease: number = 0;
28
29 // For each building, increase its height to the minimum of its row and column maximums
30 // This ensures the skyline remains unchanged from all viewing directions
31 for (let row: number = 0; row < rowCount; ++row) {
32 for (let column: number = 0; column < columnCount; ++column) {
33 // The maximum allowed height is the minimum of row and column maximums
34 const maxAllowedHeight: number = Math.min(maxHeightPerRow[row], maxHeightPerColumn[column]);
35 // Add the difference between allowed height and current height
36 totalIncrease += maxAllowedHeight - grid[row][column];
37 }
38 }
39
40 return totalIncrease;
41}
42
Time and Space Complexity
The time complexity is O(nΒ²)
, where n
is the side length of the matrix grid
.
- Computing
row_max
: Iterating through each row and finding the maximum takesO(n)
per row, withn
rows total, resulting inO(nΒ²)
. - Computing
col_max
: Thezip(*grid)
operation transposes the matrix inO(nΒ²)
time, and finding the maximum for each column takesO(n)
per column, withn
columns total, resulting inO(nΒ²)
. - The final sum operation uses nested iteration through all
nΒ²
elements of the grid, where each operation inside (accessingrow_max[i]
,col_max[j]
, and computing the difference) takesO(1)
time, resulting inO(nΒ²)
.
Overall time complexity: O(nΒ²) + O(nΒ²) + O(nΒ²) = O(nΒ²)
.
The space complexity is O(n)
, where n
is the side length of the matrix grid
.
row_max
storesn
values (one maximum per row):O(n)
.col_max
storesn
values (one maximum per column):O(n)
.- The generator expression in the sum doesn't create additional storage beyond temporary variables:
O(1)
.
Overall space complexity: O(n) + O(n) = O(n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Modifying the Original Grid
A common mistake is attempting to modify the grid in-place while calculating the increases, which can lead to incorrect results if you need to reference the original values later.
Incorrect approach:
def maxIncreaseKeepingSkyline(self, grid: List[List[int]]) -> int:
row_max = [max(row) for row in grid]
col_max = [max(col) for col in zip(*grid)]
total = 0
for i in range(len(grid)):
for j in range(len(grid[0])):
new_height = min(row_max[i], col_max[j])
total += new_height - grid[i][j]
grid[i][j] = new_height # DON'T DO THIS - modifies input
return total
Solution: Keep the grid unchanged and only calculate the differences.
2. Assuming Square Grid
The code assumes an n x n
square grid, but the problem might have rectangular grids (m x n
). Using incorrect indices can cause index out of bounds errors.
Incorrect approach:
def maxIncreaseKeepingSkyline(self, grid: List[List[int]]) -> int:
n = len(grid) # Assumes square grid
row_max = [max(grid[i]) for i in range(n)]
col_max = [max(grid[i][j] for i in range(n)) for j in range(n)] # Wrong for non-square
Solution: Handle rectangular grids properly:
def maxIncreaseKeepingSkyline(self, grid: List[List[int]]) -> int:
rows, cols = len(grid), len(grid[0]) if grid else 0
row_max = [max(row) for row in grid]
col_max = [max(grid[i][j] for i in range(rows)) for j in range(cols)]
3. Misunderstanding the Constraint
Some might think they need to increase every building to exactly min(row_max[i], col_max[j])
, but the problem asks for the maximum total increase, which means each building should be increased as much as possible (up to the constraint).
Incorrect interpretation: Thinking you need to choose which buildings to increase. Correct interpretation: Every building should be increased to its maximum allowed height.
4. Empty Grid Edge Case
Not handling empty grids or grids with empty rows can cause the code to crash.
Solution: Add validation:
def maxIncreaseKeepingSkyline(self, grid: List[List[int]]) -> int:
if not grid or not grid[0]:
return 0
# Rest of the implementation...
5. Using Wrong Transpose Method
Attempting to transpose manually with incorrect indexing is error-prone.
Incorrect approach:
col_max = []
for j in range(len(grid)):
col_max.append(max(grid[i][j] for i in range(len(grid)))) # Might fail if not square
Solution: Use zip(*grid)
which handles the transpose elegantly and works for any rectangular grid.
How many times is a tree node visited in a depth first search?
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
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
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Donβt Miss This!