2713. Maximum Strictly Increasing Cells in a Matrix
Problem Description
You are given a 1-indexed m x n
integer matrix mat
. Your goal is to find the maximum number of cells you can visit by following a specific movement rule.
Movement Rules:
- You can start from any cell in the matrix
- From your current cell, you can move to any other cell in the same row or same column
- You can only move to a cell if its value is strictly greater than your current cell's value
- You can continue moving as long as valid moves exist
The task is to determine the maximum number of cells that can be visited in a single path, starting from the optimal starting cell.
Example Understanding:
If you're at cell (i, j)
with value v
, you can move to:
- Any cell in row
i
with value >v
- Any cell in column
j
with value >v
Each move must go to a strictly increasing value, and you want to find the longest possible path through the matrix following these rules.
The function should return an integer representing the maximum number of cells that can be visited in a single path.
Intuition
Since we can only move to cells with strictly greater values, we need to process cells in increasing order of their values. This ensures that when we process a cell with value v
, we've already computed the maximum path lengths for all cells with values less than v
.
The key insight is that for any cell (i, j)
, the longest path starting from it depends on:
- The longest path we can achieve by moving within row
i
to cells with greater values - The longest path we can achieve by moving within column
j
to cells with greater values
We take the maximum of these two options and add 1 (for the current cell itself).
To efficiently track this information, we maintain:
rowMax[i]
: the length of the longest path found so far in rowi
colMax[j]
: the length of the longest path found so far in columnj
When processing cells with the same value, we need to handle them together as a group. We first calculate the new path lengths for all cells in this group (since they all have the same value and could potentially be starting points), then update the rowMax
and colMax
arrays. This two-step process prevents cells with the same value from incorrectly benefiting from each other's path lengths.
By processing values from smallest to largest and maintaining these maximum lengths for each row and column, we build up the solution dynamically. Each cell's maximum path length is 1 + max(rowMax[i], colMax[j])
, representing the best we can do by either continuing along the row or column from previously processed smaller-valued cells.
Learn more about Memoization, Binary Search, Dynamic Programming and Sorting patterns.
Solution Approach
The solution uses sorting combined with dynamic programming to efficiently compute the maximum path length.
Step 1: Group cells by value
g = defaultdict(list)
for i in range(m):
for j in range(n):
g[mat[i][j]].append((i, j))
We create a hash table g
where each key is a cell value, and the value is a list of all positions (i, j)
that contain that value. This allows us to process cells in order of their values.
Step 2: Initialize tracking arrays
rowMax = [0] * m colMax = [0] * n
rowMax[i]
tracks the maximum path length achieved in rowi
so farcolMax[j]
tracks the maximum path length achieved in columnj
so far
Step 3: Process values in ascending order
for _, pos in sorted(g.items()):
By sorting the hash table by keys, we process all cells with smaller values before cells with larger values. This ensures when we compute the path length for a cell, all potential previous cells in the path have already been processed.
Step 4: Calculate path lengths for cells with the same value
mx = []
for i, j in pos:
mx.append(1 + max(rowMax[i], colMax[j]))
ans = max(ans, mx[-1])
For each group of cells with the same value:
- Calculate the maximum path length ending at each cell as
1 + max(rowMax[i], colMax[j])
- The
1
accounts for the current cell itself max(rowMax[i], colMax[j])
gives the best path length we can inherit from either the row or column- Store these values temporarily in
mx
array to avoid updatingrowMax
andcolMax
prematurely
Step 5: Update row and column maximums
for k, (i, j) in enumerate(pos):
rowMax[i] = max(rowMax[i], mx[k])
colMax[j] = max(colMax[j], mx[k])
After calculating all path lengths for cells with the same value, update the rowMax
and colMax
arrays. This two-phase approach ensures cells with the same value don't incorrectly influence each other's calculations.
The algorithm returns the maximum path length found across all cells. The time complexity is O(mn log(mn))
due to sorting, and space complexity is O(mn)
for storing the cell positions.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example to illustrate the solution approach.
Consider the matrix:
mat = [[3, 1], [2, 4]]
Step 1: Group cells by value We create a dictionary grouping cells by their values:
g[1] = [(0,1)]
- value 1 is at position (0,1)g[2] = [(1,0)]
- value 2 is at position (1,0)g[3] = [(0,0)]
- value 3 is at position (0,0)g[4] = [(1,1)]
- value 4 is at position (1,1)
Step 2: Initialize tracking arrays
rowMax = [0, 0]
- maximum path lengths for row 0 and row 1colMax = [0, 0]
- maximum path lengths for column 0 and column 1ans = 0
- global maximum
Step 3: Process values in ascending order (1, 2, 3, 4)
Processing value 1 at position (0,1):
- Calculate path length:
1 + max(rowMax[0], colMax[1]) = 1 + max(0, 0) = 1
- Update
ans = 1
- Update arrays:
rowMax[0] = 1
,colMax[1] = 1
- Arrays now:
rowMax = [1, 0]
,colMax = [0, 1]
Processing value 2 at position (1,0):
- Calculate path length:
1 + max(rowMax[1], colMax[0]) = 1 + max(0, 0) = 1
ans
remains 1- Update arrays:
rowMax[1] = 1
,colMax[0] = 1
- Arrays now:
rowMax = [1, 1]
,colMax = [1, 1]
Processing value 3 at position (0,0):
- Calculate path length:
1 + max(rowMax[0], colMax[0]) = 1 + max(1, 1) = 2
- This means we can reach cell (0,0) from either cell (0,1) with value 1 (same row) or cell (1,0) with value 2 (same column)
- Update
ans = 2
- Update arrays:
rowMax[0] = 2
,colMax[0] = 2
- Arrays now:
rowMax = [2, 1]
,colMax = [2, 1]
Processing value 4 at position (1,1):
- Calculate path length:
1 + max(rowMax[1], colMax[1]) = 1 + max(1, 1) = 2
- From row 1: best previous path has length 1 (from cell (1,0) with value 2)
- From column 1: best previous path has length 1 (from cell (0,1) with value 1)
ans
remains 2- Update arrays:
rowMax[1] = 2
,colMax[1] = 2
Final Result: 2
The maximum path length is 2. There are two possible maximum paths:
- Start at (0,1) with value 1 → move to (0,0) with value 3 (same row)
- Start at (1,0) with value 2 → move to (0,0) with value 3 (same column)
Both paths visit exactly 2 cells, which is the maximum possible for this matrix.
Solution Implementation
1class Solution:
2 def maxIncreasingCells(self, mat: List[List[int]]) -> int:
3 # Get matrix dimensions
4 m, n = len(mat), len(mat[0])
5
6 # Group cells by their values
7 value_to_positions = defaultdict(list)
8 for i in range(m):
9 for j in range(n):
10 value_to_positions[mat[i][j]].append((i, j))
11
12 # Track maximum path length ending at each row and column
13 row_max = [0] * m
14 col_max = [0] * n
15
16 # Track the overall maximum path length
17 ans = 0
18
19 # Process cells in ascending order of their values
20 for value in sorted(value_to_positions.keys()):
21 positions = value_to_positions[value]
22
23 # Calculate max path length for all cells with current value
24 current_max_lengths = []
25 for i, j in positions:
26 # Max path length is 1 + max of previous paths in same row or column
27 max_length = 1 + max(row_max[i], col_max[j])
28 current_max_lengths.append(max_length)
29 ans = max(ans, max_length)
30
31 # Update row and column maximums with calculated values
32 # Done after all calculations to handle cells with same value correctly
33 for k, (i, j) in enumerate(positions):
34 row_max[i] = max(row_max[i], current_max_lengths[k])
35 col_max[j] = max(col_max[j], current_max_lengths[k])
36
37 return ans
38
1class Solution {
2 public int maxIncreasingCells(int[][] mat) {
3 int numRows = mat.length;
4 int numCols = mat[0].length;
5
6 // Group all cells by their values in ascending order
7 // TreeMap ensures values are processed from smallest to largest
8 TreeMap<Integer, List<int[]>> valueToPositions = new TreeMap<>();
9 for (int row = 0; row < numRows; row++) {
10 for (int col = 0; col < numCols; col++) {
11 valueToPositions.computeIfAbsent(mat[row][col], k -> new ArrayList<>())
12 .add(new int[] {row, col});
13 }
14 }
15
16 // Track the maximum path length ending at each row and column
17 int[] maxPathInRow = new int[numRows];
18 int[] maxPathInCol = new int[numCols];
19 int maxPathLength = 0;
20
21 // Process cells in ascending order of their values
22 for (var entry : valueToPositions.entrySet()) {
23 List<int[]> positions = entry.getValue();
24
25 // Calculate the maximum path length for each cell with current value
26 // Store temporarily to avoid updating row/col max while still processing
27 int[] currentLevelMax = new int[positions.size()];
28 int index = 0;
29
30 for (int[] position : positions) {
31 int row = position[0];
32 int col = position[1];
33
34 // Path length = max(previous max in same row, previous max in same col) + 1
35 currentLevelMax[index] = Math.max(maxPathInRow[row], maxPathInCol[col]) + 1;
36 maxPathLength = Math.max(maxPathLength, currentLevelMax[index]);
37 index++;
38 }
39
40 // Update row and column maximums with the computed values
41 // Done after all cells with same value are processed
42 for (int i = 0; i < currentLevelMax.length; i++) {
43 int row = positions.get(i)[0];
44 int col = positions.get(i)[1];
45 maxPathInRow[row] = Math.max(maxPathInRow[row], currentLevelMax[i]);
46 maxPathInCol[col] = Math.max(maxPathInCol[col], currentLevelMax[i]);
47 }
48 }
49
50 return maxPathLength;
51 }
52}
53
1class Solution {
2public:
3 int maxIncreasingCells(vector<vector<int>>& mat) {
4 int numRows = mat.size();
5 int numCols = mat[0].size();
6
7 // Group cells by their values, storing positions for each unique value
8 map<int, vector<pair<int, int>>> valueToPositions;
9 for (int row = 0; row < numRows; ++row) {
10 for (int col = 0; col < numCols; ++col) {
11 valueToPositions[mat[row][col]].emplace_back(row, col);
12 }
13 }
14
15 // Track the maximum path length ending at each row and column
16 vector<int> maxPathInRow(numRows);
17 vector<int> maxPathInCol(numCols);
18
19 int maxPathLength = 0;
20
21 // Process cells in ascending order of their values
22 for (auto& [value, positions] : valueToPositions) {
23 // Calculate new path lengths for all cells with the current value
24 vector<int> newPathLengths;
25 for (auto& [row, col] : positions) {
26 // Path length is 1 + max of the longest path in the same row or column
27 int currentPathLength = max(maxPathInRow[row], maxPathInCol[col]) + 1;
28 newPathLengths.push_back(currentPathLength);
29 maxPathLength = max(maxPathLength, currentPathLength);
30 }
31
32 // Update row and column maximums with the new path lengths
33 for (int idx = 0; idx < newPathLengths.size(); ++idx) {
34 auto& [row, col] = positions[idx];
35 maxPathInRow[row] = max(maxPathInRow[row], newPathLengths[idx]);
36 maxPathInCol[col] = max(maxPathInCol[col], newPathLengths[idx]);
37 }
38 }
39
40 return maxPathLength;
41 }
42};
43
1/**
2 * Finds the maximum number of cells that can be visited starting from any cell,
3 * where you can move to another cell in the same row or column with a strictly greater value
4 * @param mat - The input matrix
5 * @returns The maximum number of cells that can be visited
6 */
7function maxIncreasingCells(mat: number[][]): number {
8 const rows: number = mat.length;
9 const cols: number = mat[0].length;
10
11 // Group cells by their values for processing in ascending order
12 const valueToPositions: Map<number, Array<[number, number]>> = new Map();
13
14 // Populate the map with positions grouped by their values
15 for (let row = 0; row < rows; row++) {
16 for (let col = 0; col < cols; col++) {
17 const value = mat[row][col];
18 if (!valueToPositions.has(value)) {
19 valueToPositions.set(value, []);
20 }
21 valueToPositions.get(value)!.push([row, col]);
22 }
23 }
24
25 // Track the maximum path length achievable from each row and column
26 const maxPathFromRow: number[] = Array(rows).fill(0);
27 const maxPathFromCol: number[] = Array(cols).fill(0);
28 let maxPathLength: number = 0;
29
30 // Get all unique values and sort them in ascending order
31 const sortedValues: number[] = Array.from(valueToPositions.keys()).sort((a, b) => a - b);
32
33 // Process cells in ascending order of their values
34 for (const currentValue of sortedValues) {
35 const positions: Array<[number, number]> = valueToPositions.get(currentValue)!;
36 const currentPathLengths: number[] = [];
37
38 // Calculate the maximum path length for each position with the current value
39 for (const [row, col] of positions) {
40 // The path length is 1 plus the maximum of paths from the same row or column
41 const pathLength = 1 + Math.max(maxPathFromRow[row], maxPathFromCol[col]);
42 currentPathLengths.push(pathLength);
43 maxPathLength = Math.max(maxPathLength, pathLength);
44 }
45
46 // Update the maximum path lengths for rows and columns
47 for (let index = 0; index < positions.length; index++) {
48 const [row, col] = positions[index];
49 const pathLength = currentPathLengths[index];
50 maxPathFromRow[row] = Math.max(maxPathFromRow[row], pathLength);
51 maxPathFromCol[col] = Math.max(maxPathFromCol[col], pathLength);
52 }
53 }
54
55 return maxPathLength;
56}
57
Time and Space Complexity
Time Complexity: O(m × n × log(m × n))
The algorithm processes each cell in the matrix exactly once to build the dictionary g
, which takes O(m × n)
time. The dictionary groups cells by their values, storing their coordinates.
The sorting operation sorted(g.items())
sorts all unique values in the matrix. In the worst case, all m × n
cells have unique values, so sorting takes O(m × n × log(m × n))
time.
After sorting, the algorithm iterates through each group of cells with the same value. For each cell, it performs constant-time operations: calculating the maximum from rowMax[i]
and colMax[j]
, and updating these arrays. Since there are m × n
cells total, this iteration takes O(m × n)
time.
The dominant operation is the sorting step, giving an overall time complexity of O(m × n × log(m × n))
.
Space Complexity: O(m × n)
The dictionary g
stores the coordinates of all m × n
cells, requiring O(m × n)
space. The arrays rowMax
and colMax
use O(m)
and O(n)
space respectively, and the temporary list mx
can store at most O(m × n)
values in the worst case when all cells have the same value. Since O(m) + O(n) ≤ O(m × n)
, the overall space complexity is O(m × n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Updating row/column maximums immediately
The Problem:
A critical mistake is updating row_max
and col_max
arrays immediately while processing cells with the same value:
# INCORRECT approach
for i, j in positions:
max_length = 1 + max(row_max[i], col_max[j])
row_max[i] = max(row_max[i], max_length) # Wrong! Updates too early
col_max[j] = max(col_max[j], max_length) # Wrong! Updates too early
ans = max(ans, max_length)
Why it fails:
Consider a matrix where cells (0,0)
and (0,1)
both have value 5. If we process (0,0)
first and immediately update row_max[0]
, then when processing (0,1)
, it would incorrectly inherit the updated value from row_max[0]
, creating a false dependency between cells of equal value.
Example failure case:
Matrix: [[5, 5], [1, 2]]
- Processing cell
(0,0)
with value 5: calculates length as 1, updatesrow_max[0] = 1
- Processing cell
(0,1)
with value 5: incorrectly calculates1 + row_max[0] = 2
instead of 1
The Solution: Store calculated values in a temporary array first, then update all row/column maximums after processing all cells with the same value:
current_max_lengths = []
for i, j in positions:
max_length = 1 + max(row_max[i], col_max[j])
current_max_lengths.append(max_length)
ans = max(ans, max_length)
# Update only after all calculations are done
for k, (i, j) in enumerate(positions):
row_max[i] = max(row_max[i], current_max_lengths[k])
col_max[j] = max(col_max[j], current_max_lengths[k])
Pitfall 2: Not handling duplicate values correctly
The Problem: Assuming each cell value is unique and processing them individually rather than in groups:
# INCORRECT approach
for i in range(m):
for j in range(n):
# Process each cell independently
max_length = 1 + max(row_max[i], col_max[j])
row_max[i] = max_length
col_max[j] = max_length
Why it fails: This approach doesn't ensure cells are processed in ascending order of values, and it doesn't handle cells with equal values correctly. You cannot move between cells with equal values, so they must be processed as independent starting points at the same "level" of the dynamic programming.
The Solution: Group cells by value and process each group together, ensuring all cells with the same value are treated as potential path endpoints at the same level, with no dependencies between them.
How does merge sort divide the problem into subproblems?
Recommended Readings
Memoization Prereq Backtracking problems backtracking Memoization is a fancy word for a simple concept so is the case for a lot of things we learn in school It means saving the previous function call result in a dictionary and reading from it when we do the exact same call again
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
Want a Structured Path to Master System Design Too? Don’t Miss This!