Skip to main content

329. Max Rising Path

Longest Increasing Path in a Matrix

A must-know matrix DFS pattern. Problem 329 is a classic example.

Goal is to find the longest strictly increasing path in a matrix.

Constraint is that from any cell, when trying to extend a path, only four cardinal directions (east, south, west, north) are allowed — no diagonals.

Of course, stepping outside the matrix is also forbidden, so boundary checks come first.

Scanning Effortlessly

For every element matrix[i][j] in the matrix,

we try using it as a starting point to find the longest increasing path from there.

So we create maxRisingPath, a 2D array with the same dimensions as matrix.

Initially, all elements of maxRisingPath are set to -1, meaning unvisited.

Then for each matrix[i][j], we check its four potential neighbors:

  1. matrix[i][j + 1] — east
  2. matrix[i + 1][j] — south
  3. matrix[i][j - 1] — west
  4. matrix[i - 1][j] — north

When checking neighbor xx, once we confirm xx is within bounds,

we then check whether xx's value is strictly greater than matrix[i][j].

If so, we look up the longest increasing path starting from xx.

Adding 1 gives us the longest increasing path from matrix[i][j] going through xx.

So if neighbor xx shows -1 in maxRisingPath,

it means xx hasn't been visited yet — we run DFS on xx first.

Once xx's longest path length is determined, say kk, we apply:

maxRisingPath[i][j]=max(maxRisingPath[i][j],1+k)maxRisingPath[i][j] = max(maxRisingPath[i][j], 1 + k)

After updating maxRisingPath[i][j], don't forget to compare it with global maximum.

Matrix_DFS Efficiency Both time and space complexity are O(mn)O(mn), where mm and nn are number of rows and columns.

Problem 329 is officially labeled hard, but it's actually not difficult. Even relatively easy.

A great problem to build fundamentals.


class MaxRisingPath: # LeetCode Q.329.
def __init__(self):
self.matrix = []
self.max_rising_path = []
self.max_rising_len = 1

def find_longest_rising_path(self, matrix: list[list[int]]) -> int:
self.matrix = matrix

# An entry's max rising path value is -1 if this entry is unsearched.
# Otherwise, max rising path value is the finalized value w.r.t. entry.
self.max_rising_path = [[-1] * len(matrix[0]) for _ in range(len(matrix))]

self.max_rising_len = 1 # Base case.

for row_idx in range(len(matrix)):
for col_idx in range(len(matrix[0])):
if self.max_rising_path[row_idx][col_idx] == -1:
self._dfs_rising_path(row_idx, col_idx)

return self.max_rising_len

def _dfs_rising_path(self, row_idx: int, col_idx: int) -> None:
self.max_rising_path[row_idx][col_idx] = 1 # Base case: a path of only current entry.

neighbors: list[tuple[int, int]] = [
(row_idx + 1, col_idx), (row_idx - 1, col_idx), # South & North.
(row_idx, col_idx + 1), (row_idx, col_idx - 1) # East & West.
]

for neighbor_row_idx, neighbor_col_idx in neighbors:
# Inbound checks on row & col indices.
if not (0 <= neighbor_row_idx < len(self.matrix)): continue

if not (0 <= neighbor_col_idx < len(self.matrix[0])): continue

# Only consider neighbors that are bigger than current entry.
if self.matrix[row_idx][col_idx] < self.matrix[neighbor_row_idx][neighbor_col_idx]:
# Unsearched neighbor.
if self.max_rising_path[neighbor_row_idx][neighbor_col_idx] == -1:
self._dfs_rising_path(neighbor_row_idx, neighbor_col_idx)

rising_len = 1 + self.max_rising_path[neighbor_row_idx][neighbor_col_idx]

if rising_len > self.max_rising_path[row_idx][col_idx]:
self.max_rising_path[row_idx][col_idx] = rising_len

if rising_len > self.max_rising_len: self.max_rising_len = rising_len