Dynamic Programming · 57 of 60
Maximum sum rectangle in a 2D matrix
Find the rectangle of numbers in a grid with the biggest total by squashing column strips into one line and running the best stretch trick.
- Simple way
- time O(R^2 * C^2)
room O(R * C) - Clever way
- time O(C^2 * R)
room O(R)
The problem, simply
You get a grid of whole numbers with R rows and C columns. Some are negative. Choose a rectangle: a top row, a bottom row, a left column and a right column. Add up every number inside.
Give back the biggest total any rectangle can have. The rectangle must hold at least one cell, so if every number is negative, the answer is the least negative single number.
A story to remember it
Picture it
A field of numbers. The best rectangle is rows 1 to 3 and columns 1 to 3. Its numbers add up to 29.
- answer
Examples
A normal case
- In
[[1,2,-1,-4,-20],[-8,-3,4,2,1],[3,8,10,1,3],[-4,-1,1,7,-6]]- Out
29
Rows 1 to 3, columns 1 to 3: (-3 + 4 + 2) + (8 + 10 + 1) + (-1 + 1 + 7) = 3 + 19 + 7 = 29.
All positive
- In
[[1,2],[3,4]]- Out
10
Every number helps, so take the whole grid.
All negative
- In
[[-1,-2],[-3,-4]]- Out
-1
Any bigger rectangle adds more negatives. The best is the single cell -1.
One cell
- In
[[5]]- Out
5
The only rectangle is the cell itself.
Way 1: the simple way
Try every rectangle, but make adding it up instant with a table of corner sums.
- Build
pre[i][j], the sum of everything above and to the left of cell(i, j), including the row and column before it. - The sum of rows
t..band columnsl..rispre[b+1][r+1] - pre[t][r+1] - pre[b+1][l] + pre[t][l]. - Loop over all
t <= band alll <= rand keep the best.
class Solution {
public int maxRectSum(int[][] g) {
int R = g.length, C = g[0].length;
int[][] pre = new int[R + 1][C + 1]; // corner sums with a zero border
for (int i = 0; i < R; i++)
for (int j = 0; j < C; j++)
pre[i + 1][j + 1] = g[i][j] + pre[i][j + 1] + pre[i + 1][j] - pre[i][j];
int best = Integer.MIN_VALUE;
for (int t = 0; t < R; t++)
for (int b = t; b < R; b++)
for (int l = 0; l < C; l++)
for (int r = l; r < C; r++) {
int sum = pre[b + 1][r + 1] - pre[t][r + 1] - pre[b + 1][l] + pre[t][l];
best = Math.max(best, sum);
}
return best;
}
}How long it takes: about R^2 / 2 row pairs times C^2 / 2 column pairs, O(R^2 * C^2). Extra room: the corner sum table, O(R * C). Without that table, adding up each rectangle by hand would be O(R^3 * C^3).
Way 2: the clever way
Fix the left column left. Move the right column right one step at a time, and keep rowSum[i], the sum of row i from left to right. Each step only adds one new column to each row sum.
For each strip, run the best stretch trick on rowSum to find the best top and bottom rows:
curis the best stretch ending at this row. It ismax(rowSum[i], cur + rowSum[i]).bestis the biggestcurseen.
Columns 1 to 3 squashed: each row becomes one number. Row sums are -3, 3, 19, 7. The best stretch is rows 1 to 3: 3 + 19 + 7 = 29.
- answer
- path
The animation runs every strip. The highlighted column band is the strip, and the bright rows are the best stretch inside it.
Squash every strip of columns, then find the best stretch of rows
01 / 17
Pick a left column and a right column. Add up each row between them. That squashes the strip into one column of row sums, and we run the biggest stretch trick on it.
- best
- -
- checking
- answer
- path
- just added
class Solution {
public int maxRectSum(int[][] g) {
int R = g.length, C = g[0].length;
int best = Integer.MIN_VALUE;
for (int left = 0; left < C; left++) {
int[] rowSum = new int[R]; // row sums for the strip left..right
for (int right = left; right < C; right++) {
for (int i = 0; i < R; i++) rowSum[i] += g[i][right]; // add one column
best = Math.max(best, bestStretch(rowSum));
}
}
return best;
}
private int bestStretch(int[] a) { // Kadane: biggest sum of a stretch
int cur = a[0], best = a[0];
for (int i = 1; i < a.length; i++) {
cur = Math.max(a[i], cur + a[i]); // carry on, or start fresh
best = Math.max(best, cur);
}
return best;
}
}How long it takes: C^2 / 2 strips, and each strip costs O(R) to update and scan, so O(C^2 * R). Extra room: one rowSum array, O(R). If the grid has more columns than rows, loop over row pairs instead and squash columns, so the squared part is the smaller side: O(min(R,C)^2 * max(R,C)).
Watch out for
- Reset
rowSumwhenleftmoves, not whenrightmoves. - Sums of many large numbers can overflow
int. Uselongif values can be big. - To return the rectangle, remember
left,rightand the stretch's start and end rows wheneverbestimproves.
What interviewers ask next
- Biggest rectangle with sum at most
k(LeetCode 363)? Same strips, but for each strip use running sums and a sorted set to find the smallest earlier running sum that is at leastcurrent - k. That isO(C^2 * R log R). - Largest rectangle with sum exactly 0? Same strips, and a hash map of running sums to find two equal ones.
- Biggest square instead of rectangle? Use the corner sum table and loop over the square size.
- Can it go below
O(n^3)for annbyngrid? Only with heavy theory. In interviewsO(n^3)is the expected answer.
Remember this
Grown-up words: the fence is a sub-matrix, the corner sum table is a 2D prefix sum, squashing a strip is column compression, and the best stretch trick is Kadane's algorithm, which is a tiny 1D DP.