Longest Increasing Path in a Matrix
Picture
In [[1,2],[4,3]], the increasing route 1→2→3→4 moves right, down, and left. A row-by-row grid DP would not respect its dependencies. But strict increase means the directed movement graph cannot have a cycle.
Predict
What is the best route length starting at a cell with no larger neighbor?
Walk through the small example
One: the cell itself. Other cells can add themselves to the best larger-neighbor continuation.
Mental model and complete Python solution
Define length[u]=1+max(length[v]) over larger neighbors, with empty maximum zero. Those larger-neighbor answers must be ready first. Count each cell’s larger neighbors, seed local maxima with length one, and process them in a reverse topological queue. A finished cell offers length+1 to each smaller neighbor; that neighbor becomes ready when all of its larger neighbors are processed. This computes the same recurrence as memoized DFS without a potentially RC-deep Python call stack.
What the function promises: A nonempty rectangular matrix permits four-direction moves only to strictly larger values. Return the maximum number of cells in a valid path. Empty input returns zero as a helper extension.
from collections import deque
def longest_increasing_path(matrix):
if not matrix or not matrix[0]:
return 0
rows, cols = len(matrix), len(matrix[0])
def neighbors(r, c):
for nr, nc in ((r-1,c), (r+1,c), (r,c-1), (r,c+1)):
if 0 <= nr < rows and 0 <= nc < cols:
yield nr, nc
larger = [[0] * cols for _ in range(rows)]
length = [[1] * cols for _ in range(rows)]
ready = deque()
for r in range(rows):
for c in range(cols):
larger[r][c] = sum(matrix[nr][nc] > matrix[r][c]
for nr, nc in neighbors(r, c))
if larger[r][c] == 0:
ready.append((r, c))
answer = 1
while ready:
r, c = ready.popleft()
answer = max(answer, length[r][c])
for nr, nc in neighbors(r, c):
if matrix[nr][nc] < matrix[r][c]:
length[nr][nc] = max(length[nr][nc], length[r][c] + 1)
larger[nr][nc] -= 1
if larger[nr][nc] == 0:
ready.append((nr, nc))
return answer
assert longest_increasing_path([[1,2], [4,3]]) == 4
assert longest_increasing_path([[7,7], [7,7]]) == 1
assert longest_increasing_path([[1,2,3,4,5]]) == 5
assert longest_increasing_path([]) == 0
Work and storage: O(RC) time and space; each cell and its at-most-four edges are processed a constant number of times. Sorting by value would also supply a valid order but adds logarithmic sorting work.
When to recognize it: An edge always leads to a bigger value. A cycle would need to become bigger and return to the same value: impossible.
Check before applying it: Allowing equal or decreasing values removes the strict-progress proof. Ordinary memoization is not a general solution to cyclic longest paths.
Change one rule
Equal-valued moves become legal and revisits remain allowed. Does the recurrence still define a finite longest walk?
See what changes
Not necessarily. Two equal adjacent cells allow arbitrarily many back-and-forth steps. If instead revisits are forbidden, the problem becomes a longest-simple-path problem and generally needs additional history; a memo keyed only by cell is no longer justified.
Connect
Parallel Courses III: Reuse longest-path reasoning after discovering the hidden DAG.
Shortest Path in a Grid with Obstacles Elimination: Four grid directions alone do not tell you whether to use DP or BFS.
Problem reference: Longest Increasing Path in a Matrix (Hard). The walkthrough and Python solution above are original course material.