Matrix · 7 of 10
Find a specific pair in matrix
Find the biggest jump from one cell to another cell that is strictly lower and strictly further right, using a table of the biggest number below and to the right.
- Simple way
- time O(n^4)
room O(1) - Clever way
- time O(n^2)
room O(n^2)
The problem, simply
You get an n by n grid of whole numbers, which can be negative. The grid is not sorted in any way.
Pick two cells. The first is at row a, column b. The second is at row c, column d, and it must be strictly lower and strictly further right: c > a and d > b. Find the biggest possible value of mat[c][d] - mat[a][b].
A story to remember it
Picture it
If the first cell is -8 at row 1, column 0, its partner must be in the shaded block: a lower row and a further right column. The biggest number in that block is 10, so this pair gives 10 - (-8) = 18.
- looking now
- answer
- path
Examples
The classic grid
- In
[[1,2,-1,-4,-20],[-8,-3,4,2,1],[3,8,6,1,3],[-4,-1,1,7,-6],[0,-4,10,-5,1]]- Out
18
Start at -8 (row 1, column 0) and end at 10 (row 4, column 2). 10 - (-8) = 18.
A sorted grid
- In
[[1,2,3],[4,5,6],[7,8,9]]- Out
8
The smallest number is at the top left and the biggest at the bottom right, and they are a legal pair: 9 - 1 = 8.
The answer can be negative
- In
[[9,8],[7,1]]- Out
-8
The only legal pair is 9 then 1. The answer is 1 - 9 = -8. Code that starts the answer at 0 gets this wrong.
Same row does not count
- In
[[1,100],[2,3]]- Out
2
100 is in the same row as 1, so it is not allowed. The only legal pair is 1 then 3, so the answer is 2.
Too small
- In
[[5]]- Out
no pair
A 1 by 1 grid has no cell that is lower and further right. Return a marker such as Integer.MIN_VALUE, or say so.
Way 1: the simple way
Try every start cell with every end cell that is lower and further right, and keep the best difference.
- For each start
(a, b)that is not in the last row or column. - For each end
(c, d)withc > aandd > b. - Keep the biggest
mat[c][d] - mat[a][b].
class Solution {
public int findMaxValue(int[][] mat) {
int n = mat.length;
int best = Integer.MIN_VALUE;
for (int a = 0; a < n - 1; a++)
for (int b = 0; b < n - 1; b++) // every start cell
for (int c = a + 1; c < n; c++)
for (int d = b + 1; d < n; d++) // every end cell lower and further right
best = Math.max(best, mat[c][d] - mat[a][b]);
return best;
}
}How long it takes: four loops of size up to n, O(n^4). Extra room: O(1).
Way 2: the clever way
For a start cell (i, j), the best end is the biggest number in the block of rows i+1 and down, columns j+1 and right. So build a table big where big[i][j] is the biggest number at (i, j) or anywhere below and to the right of it. Then the best end for start (i, j) is big[i+1][j+1].
The table is easy to fill from the bottom right corner, because each block is the cell itself plus the block below it plus the block to its right:
big[i][j] = max(mat[i][j], big[i+1][j], big[i][j+1])
- Fill the last row from right to left, and the last column from bottom to top.
- Go through the other cells from the bottom right toward the top left. For each one, first try it as a start:
big[i+1][j+1] - mat[i][j]. Then fillbig[i][j].
Fill the biggest below right table from the corner
01 / 11
The right table will hold, for every cell, the biggest number at that cell or anywhere below and to the right of it. We fill it from the bottom right corner.
- answer
- none yet
- looking now
- checking
- seen
- answer
- path
- just added
class Solution {
public int findMaxValue(int[][] mat) {
int n = mat.length;
if (n < 2) return Integer.MIN_VALUE; // no legal pair
int[][] big = new int[n][n]; // biggest at or below right of each cell
big[n - 1][n - 1] = mat[n - 1][n - 1];
for (int j = n - 2; j >= 0; j--) big[n - 1][j] = Math.max(mat[n - 1][j], big[n - 1][j + 1]); // last row
for (int i = n - 2; i >= 0; i--) big[i][n - 1] = Math.max(mat[i][n - 1], big[i + 1][n - 1]); // last column
int best = Integer.MIN_VALUE;
for (int i = n - 2; i >= 0; i--) {
for (int j = n - 2; j >= 0; j--) {
best = Math.max(best, big[i + 1][j + 1] - mat[i][j]); // this cell as the start
big[i][j] = Math.max(mat[i][j], Math.max(big[i + 1][j], big[i][j + 1]));
}
}
return best;
}
}How long it takes: every cell is filled once with a few comparisons, O(n^2). Extra room: the table, O(n^2).
Watch out for
- Starting
bestat 0. If every legal pair goes down, the true answer is negative. - Filling the table in the wrong order.
big[i+1][j]andbig[i][j+1]must already be ready, so go from the bottom right toward the top left. - Sorting assumptions. Some write ups say the grid is sorted. It is not, and the method does not need it. If it were sorted, the answer would be the bottom right minus the top left.
- Very large numbers: the difference of two
intvalues can overflow. Uselongif values can be near theintlimits.
What interviewers ask next
- Can you use less room? Yes. When you fill row
i, you only read rowi + 1. Keep one array for the row below and one for the current row, soO(n)room. - Can you also return the two cells? Store, next to each
bigvalue, where that biggest number lives. Whenbestimproves, record(i, j)and that position. - The one row version: the biggest
a[j] - a[i]withj > i. Walk from the right keeping the biggest number seen. That is the same idea in one direction, like the best time to buy and sell a stock. - What if the end may share a row or a column with the start (
c >= a,d >= b, but not the same cell)? Usemax(big[i+1][j], big[i][j+1])as the best partner. Those two blocks cover everything except the cell itself. - A rectangle version: the biggest sum of any sub grid. That is a different problem (Kadane's idea on pairs of rows), but interviewers like to ask it next.
Remember this
Grown-up words: the numbers written on the map form a suffix maximum table (a small 2D dynamic programming table), filling from the corner is the order that makes each cell depend only on finished cells, and trying every start with one lookup is what turns O(n^4) into O(n^2).