1594. Maximum Non Negative Product in a Matrix
Problem Description
You have a m x n matrix filled with integers. Your task is to find a path from the top-left corner (0, 0) to the bottom-right corner (m-1, n-1) that gives the maximum non-negative product.
Movement Rules:
- Start at position
(0, 0)(top-left corner) - You can only move right or down at each step
- End at position
(m-1, n-1)(bottom-right corner)
Product Calculation:
- The product of a path is calculated by multiplying all the numbers in the cells you visit along that path
- You want to find the path that gives the maximum product that is non-negative (≥ 0)
Return Value:
- If the maximum product is non-negative, return it modulo
10^9 + 7 - If the maximum product is negative (all possible paths result in negative products), return
-1
Important Note: The modulo operation is applied after finding the maximum product, not during the calculation.
Example: If you have a grid like:
[2, 1, 3] [0, 1, -2]
Some possible paths from (0,0) to (1,2) are:
- Path 1:
2 → 1 → 3 → -2with product =2 × 1 × 3 × (-2) = -12 - Path 2:
2 → 0 → 1 → -2with product =2 × 0 × 1 × (-2) = 0
The maximum non-negative product would be 0 in this case.
Intuition
When dealing with products, we need to consider that negative numbers can become positive when multiplied by another negative number. This means a very small (most negative) value could potentially become the largest positive value if we multiply it by a negative number later in the path.
Consider this scenario: if we have a path with product -100 and we encounter a cell with value -2, the product becomes 200. Meanwhile, a path with product 50 encountering the same -2 would only result in -100.
This leads us to a key insight: we need to track both the minimum and maximum products at each cell, because:
- The minimum product (most negative) could become the maximum when multiplied by a negative number
- The maximum product could become the minimum when multiplied by a negative number
For each cell (i, j), we need to know:
- The minimum product possible to reach this cell
- The maximum product possible to reach this cell
When we move to a new cell with value v:
- If
v ≥ 0:- The new minimum = (smaller of the two previous minimums) ×
v - The new maximum = (larger of the two previous maximums) ×
v
- The new minimum = (smaller of the two previous minimums) ×
- If
v < 0:- The new minimum = (larger of the two previous maximums) ×
v(positive becomes negative) - The new maximum = (smaller of the two previous minimums) ×
v(negative becomes positive)
- The new minimum = (larger of the two previous maximums) ×
By maintaining both extremes throughout our path, we ensure we don't miss any opportunity where a negative intermediate result could lead to the optimal positive result later.
The dynamic programming approach naturally fits here because:
- Each cell's result only depends on its immediate predecessors (cell above or cell to the left)
- We can build up the solution incrementally from top-left to bottom-right
- The subproblems (finding min/max products to reach each cell) overlap and can be reused
Learn more about Dynamic Programming patterns.
Solution Approach
We implement a dynamic programming solution using a 3D array dp[i][j][k] where:
dp[i][j][0]stores the minimum product to reach cell(i, j)dp[i][j][1]stores the maximum product to reach cell(i, j)
Step 1: Initialize the DP array
dp = [[[grid[0][0]] * 2 for _ in range(n)] for _ in range(m)]
The starting cell (0, 0) has both minimum and maximum values equal to grid[0][0].
Step 2: Fill the first column
for i in range(1, m):
dp[i][0] = [dp[i - 1][0][0] * grid[i][0]] * 2
For cells in the first column, we can only arrive from above. Since there's only one path, both min and max are the same: the product of all cells from (0, 0) to (i, 0).
Step 3: Fill the first row
for j in range(1, n):
dp[0][j] = [dp[0][j - 1][0] * grid[0][j]] * 2
Similarly, for the first row, we can only arrive from the left, so both min and max are the cumulative product.
Step 4: Fill the rest of the grid
For each cell (i, j) where i ≥ 1 and j ≥ 1:
We can arrive from either (i-1, j) (above) or (i, j-1) (left). The current cell value is v = grid[i][j].
If v ≥ 0 (non-negative):
dp[i][j][0] = min(dp[i - 1][j][0], dp[i][j - 1][0]) * v
dp[i][j][1] = max(dp[i - 1][j][1], dp[i][j - 1][1]) * v
- Minimum stays minimum when multiplied by positive
- Maximum stays maximum when multiplied by positive
If v < 0 (negative):
dp[i][j][0] = max(dp[i - 1][j][1], dp[i][j - 1][1]) * v
dp[i][j][1] = min(dp[i - 1][j][0], dp[i][j - 1][0]) * v
- The largest positive becomes the smallest (most negative) when multiplied by negative
- The smallest (most negative) becomes the largest positive when multiplied by negative
Step 5: Return the result
ans = dp[-1][-1][1] return -1 if ans < 0 else ans % mod
The maximum product to reach the bottom-right corner is stored in dp[m-1][n-1][1]. If it's negative, return -1; otherwise, return the value modulo 10^9 + 7.
Time Complexity: O(m × n) - we visit each cell once
Space Complexity: O(m × n) - for the DP array storing min/max for each cell
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 this 3×3 grid:
[-1, 2, 3] [ 4, -5, 6] [ 7, 8, -9]
We'll track both minimum and maximum products at each cell using our DP approach.
Step 1: Initialize
dp[0][0]: min = -1, max = -1 (starting cell value)
Step 2: Fill first row
dp[0][1]: Only reachable from left- min = max = -1 × 2 = -2
dp[0][2]: Only reachable from left- min = max = -2 × 3 = -6
Step 3: Fill first column
dp[1][0]: Only reachable from above- min = max = -1 × 4 = -4
dp[2][0]: Only reachable from above- min = max = -4 × 7 = -28
Step 4: Fill remaining cells
For dp[1][1] (value = -5, negative):
- From above: min = -2, max = -2
- From left: min = -4, max = -4
- Since -5 is negative, we swap min/max logic:
- New min = max(-2, -4) × (-5) = -2 × (-5) = 10
- New max = min(-2, -4) × (-5) = -4 × (-5) = 20
For dp[1][2] (value = 6, positive):
- From above: min = -6, max = -6
- From left: min = 10, max = 20
- Since 6 is positive:
- New min = min(-6, 10) × 6 = -6 × 6 = -36
- New max = max(-6, 20) × 6 = 20 × 6 = 120
For dp[2][1] (value = 8, positive):
- From above: min = 10, max = 20
- From left: min = -28, max = -28
- Since 8 is positive:
- New min = min(10, -28) × 8 = -28 × 8 = -224
- New max = max(20, -28) × 8 = 20 × 8 = 160
For dp[2][2] (value = -9, negative):
- From above: min = -36, max = 120
- From left: min = -224, max = 160
- Since -9 is negative, we swap:
- New min = max(120, 160) × (-9) = 160 × (-9) = -1440
- New max = min(-36, -224) × (-9) = -224 × (-9) = 2016
Step 5: Return result
The maximum product at dp[2][2] is 2016, which is non-negative.
Return: 2016 % (10^9 + 7) = 2016
The optimal path that gives us 2016 is: -1 → 4 → -5 → 6 → 8 → -9 Product: (-1) × 4 × (-5) × 8 × (-9) = 20 × 8 × (-9) = -1440 (Wait, this doesn't match!)
Let me recalculate the path correctly: Actually, the path is: -1 → 4 → 7 → 8 → -9 Product: (-1) × 4 × 7 × 8 × (-9) = -28 × 8 × (-9) = 2016 ✓
This example demonstrates how tracking both minimum and maximum values is crucial - the negative minimum (-224) at cell (2,1) became our maximum (2016) when multiplied by the negative value (-9) at the destination.
Solution Implementation
1class Solution:
2 def maxProductPath(self, grid: List[List[int]]) -> int:
3 # Get grid dimensions
4 rows, cols = len(grid), len(grid[0])
5 MOD = 10**9 + 7
6
7 # Initialize DP table: dp[i][j] = [min_product, max_product] at cell (i, j)
8 # Each cell stores both minimum and maximum products to handle negative numbers
9 dp = [[[grid[0][0], grid[0][0]] for _ in range(cols)] for _ in range(rows)]
10
11 # Initialize first column (can only come from above)
12 for row in range(1, rows):
13 prev_product = dp[row - 1][0][0]
14 current_value = grid[row][0]
15 dp[row][0][0] = prev_product * current_value # min product
16 dp[row][0][1] = prev_product * current_value # max product
17
18 # Initialize first row (can only come from left)
19 for col in range(1, cols):
20 prev_product = dp[0][col - 1][0]
21 current_value = grid[0][col]
22 dp[0][col][0] = prev_product * current_value # min product
23 dp[0][col][1] = prev_product * current_value # max product
24
25 # Fill the DP table for remaining cells
26 for row in range(1, rows):
27 for col in range(1, cols):
28 current_value = grid[row][col]
29
30 if current_value >= 0:
31 # For non-negative values:
32 # - Minimum comes from multiplying with previous minimum
33 # - Maximum comes from multiplying with previous maximum
34 min_from_above = dp[row - 1][col][0]
35 min_from_left = dp[row][col - 1][0]
36 dp[row][col][0] = min(min_from_above, min_from_left) * current_value
37
38 max_from_above = dp[row - 1][col][1]
39 max_from_left = dp[row][col - 1][1]
40 dp[row][col][1] = max(max_from_above, max_from_left) * current_value
41 else:
42 # For negative values:
43 # - Minimum comes from multiplying with previous maximum (sign flip)
44 # - Maximum comes from multiplying with previous minimum (sign flip)
45 max_from_above = dp[row - 1][col][1]
46 max_from_left = dp[row][col - 1][1]
47 dp[row][col][0] = max(max_from_above, max_from_left) * current_value
48
49 min_from_above = dp[row - 1][col][0]
50 min_from_left = dp[row][col - 1][0]
51 dp[row][col][1] = min(min_from_above, min_from_left) * current_value
52
53 # Get the maximum product at the bottom-right cell
54 max_product = dp[-1][-1][1]
55
56 # Return -1 if the maximum product is negative, otherwise return modulo result
57 return -1 if max_product < 0 else max_product % MOD
581class Solution {
2 // Modulo value for the result
3 private static final int MOD = (int) 1e9 + 7;
4
5 public int maxProductPath(int[][] grid) {
6 int rows = grid.length;
7 int cols = grid[0].length;
8
9 // dp[i][j][0] stores the minimum product to reach cell (i, j)
10 // dp[i][j][1] stores the maximum product to reach cell (i, j)
11 // We track both min and max because negative numbers can flip the relationship
12 long[][][] dp = new long[rows][cols][2];
13
14 // Initialize starting cell with its value for both min and max
15 dp[0][0][0] = grid[0][0];
16 dp[0][0][1] = grid[0][0];
17
18 // Initialize first column (can only come from above)
19 for (int i = 1; i < rows; i++) {
20 dp[i][0][0] = dp[i - 1][0][0] * grid[i][0];
21 dp[i][0][1] = dp[i - 1][0][1] * grid[i][0];
22 }
23
24 // Initialize first row (can only come from left)
25 for (int j = 1; j < cols; j++) {
26 dp[0][j][0] = dp[0][j - 1][0] * grid[0][j];
27 dp[0][j][1] = dp[0][j - 1][1] * grid[0][j];
28 }
29
30 // Fill the dp table for remaining cells
31 for (int i = 1; i < rows; i++) {
32 for (int j = 1; j < cols; j++) {
33 int currentValue = grid[i][j];
34
35 if (currentValue >= 0) {
36 // For positive values:
37 // - Minimum stays minimum after multiplication
38 // - Maximum stays maximum after multiplication
39 dp[i][j][0] = Math.min(dp[i - 1][j][0], dp[i][j - 1][0]) * currentValue;
40 dp[i][j][1] = Math.max(dp[i - 1][j][1], dp[i][j - 1][1]) * currentValue;
41 } else {
42 // For negative values:
43 // - Previous maximum becomes new minimum after multiplication
44 // - Previous minimum becomes new maximum after multiplication
45 dp[i][j][0] = Math.max(dp[i - 1][j][1], dp[i][j - 1][1]) * currentValue;
46 dp[i][j][1] = Math.min(dp[i - 1][j][0], dp[i][j - 1][0]) * currentValue;
47 }
48 }
49 }
50
51 // Get the maximum product at the destination cell
52 long maxProduct = dp[rows - 1][cols - 1][1];
53
54 // Return -1 if the maximum product is negative, otherwise return modulo result
55 return maxProduct < 0 ? -1 : (int) (maxProduct % MOD);
56 }
57}
581using ll = long long;
2const int MOD = 1e9 + 7;
3
4class Solution {
5public:
6 int maxProductPath(vector<vector<int>>& grid) {
7 int rows = grid.size();
8 int cols = grid[0].size();
9
10 // dp[i][j][0] stores minimum product to reach (i,j)
11 // dp[i][j][1] stores maximum product to reach (i,j)
12 // We track both min and max because negative numbers can flip min to max
13 vector<vector<vector<ll>>> dp(rows, vector<vector<ll>>(cols, vector<ll>(2)));
14
15 // Initialize starting point
16 dp[0][0][0] = grid[0][0]; // min at (0,0)
17 dp[0][0][1] = grid[0][0]; // max at (0,0)
18
19 // Initialize first column (can only come from above)
20 for (int i = 1; i < rows; ++i) {
21 dp[i][0][0] = dp[i - 1][0][0] * grid[i][0];
22 dp[i][0][1] = dp[i - 1][0][1] * grid[i][0];
23 }
24
25 // Initialize first row (can only come from left)
26 for (int j = 1; j < cols; ++j) {
27 dp[0][j][0] = dp[0][j - 1][0] * grid[0][j];
28 dp[0][j][1] = dp[0][j - 1][1] * grid[0][j];
29 }
30
31 // Fill the dp table for remaining cells
32 for (int i = 1; i < rows; ++i) {
33 for (int j = 1; j < cols; ++j) {
34 int currentValue = grid[i][j];
35
36 if (currentValue >= 0) {
37 // For positive numbers:
38 // - minimum stays minimum after multiplication
39 // - maximum stays maximum after multiplication
40 dp[i][j][0] = min(dp[i - 1][j][0], dp[i][j - 1][0]) * currentValue;
41 dp[i][j][1] = max(dp[i - 1][j][1], dp[i][j - 1][1]) * currentValue;
42 } else {
43 // For negative numbers:
44 // - maximum becomes minimum after multiplication
45 // - minimum becomes maximum after multiplication
46 dp[i][j][0] = max(dp[i - 1][j][1], dp[i][j - 1][1]) * currentValue;
47 dp[i][j][1] = min(dp[i - 1][j][0], dp[i][j - 1][0]) * currentValue;
48 }
49 }
50 }
51
52 // Get the maximum product at destination
53 ll maxProduct = dp[rows - 1][cols - 1][1];
54
55 // Return -1 if the maximum product is negative, otherwise return modulo result
56 return maxProduct < 0 ? -1 : static_cast<int>(maxProduct % MOD);
57 }
58};
591type ll = number;
2const MOD = 1e9 + 7;
3
4function maxProductPath(grid: number[][]): number {
5 const rows = grid.length;
6 const cols = grid[0].length;
7
8 // dp[i][j][0] stores minimum product to reach cell (i, j)
9 // dp[i][j][1] stores maximum product to reach cell (i, j)
10 // We track both min and max because negative numbers can flip min to max
11 const dp: number[][][] = Array(rows).fill(null).map(() =>
12 Array(cols).fill(null).map(() => Array(2).fill(0))
13 );
14
15 // Initialize starting point at (0, 0)
16 dp[0][0][0] = grid[0][0]; // minimum at (0, 0)
17 dp[0][0][1] = grid[0][0]; // maximum at (0, 0)
18
19 // Initialize first column (can only be reached from above)
20 for (let i = 1; i < rows; i++) {
21 dp[i][0][0] = dp[i - 1][0][0] * grid[i][0];
22 dp[i][0][1] = dp[i - 1][0][1] * grid[i][0];
23 }
24
25 // Initialize first row (can only be reached from left)
26 for (let j = 1; j < cols; j++) {
27 dp[0][j][0] = dp[0][j - 1][0] * grid[0][j];
28 dp[0][j][1] = dp[0][j - 1][1] * grid[0][j];
29 }
30
31 // Fill the dp table for remaining cells
32 for (let i = 1; i < rows; i++) {
33 for (let j = 1; j < cols; j++) {
34 const currentValue = grid[i][j];
35
36 if (currentValue >= 0) {
37 // For positive or zero values:
38 // - minimum stays minimum after multiplication
39 // - maximum stays maximum after multiplication
40 dp[i][j][0] = Math.min(dp[i - 1][j][0], dp[i][j - 1][0]) * currentValue;
41 dp[i][j][1] = Math.max(dp[i - 1][j][1], dp[i][j - 1][1]) * currentValue;
42 } else {
43 // For negative values:
44 // - maximum becomes minimum after multiplication (sign flips)
45 // - minimum becomes maximum after multiplication (sign flips)
46 dp[i][j][0] = Math.max(dp[i - 1][j][1], dp[i][j - 1][1]) * currentValue;
47 dp[i][j][1] = Math.min(dp[i - 1][j][0], dp[i][j - 1][0]) * currentValue;
48 }
49 }
50 }
51
52 // Get the maximum product at destination (bottom-right corner)
53 const maxProduct = dp[rows - 1][cols - 1][1];
54
55 // Return -1 if the maximum product is negative, otherwise return modulo result
56 return maxProduct < 0 ? -1 : maxProduct % MOD;
57}
58Time and Space Complexity
Time Complexity: O(m * n) where m is the number of rows and n is the number of columns in the grid.
The algorithm iterates through each cell of the grid exactly once after initialization:
- Initializing the first row takes
O(m)time - Initializing the first column takes
O(n)time - The nested loops iterate through the remaining
(m-1) * (n-1)cells, performing constant time operations for each cell - Total time:
O(m) + O(n) + O(m * n) = O(m * n)
Space Complexity: O(m * n)
The space is dominated by the 3D DP array dp which has dimensions m × n × 2:
- Each cell in the grid corresponds to an entry in
dpthat stores two values (minimum and maximum products) - Total space used:
m * n * 2 = O(m * n) - Additional variables (
m,n,mod,v,ans) useO(1)space - Overall space complexity:
O(m * n)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Handling of Zero Values
Pitfall: When a cell contains 0, the product becomes 0, which might seem straightforward. However, a common mistake is not recognizing that 0 can actually be the optimal path when all other paths lead to negative products. Some implementations might incorrectly skip or special-case zeros.
Solution: Treat 0 like any other non-negative number. When multiplying by 0, both min and max become 0, which correctly propagates through the DP table.
2. Applying Modulo During Computation
Pitfall: A critical error is applying the modulo operation during the DP computation:
# WRONG - Don't do this!
dp[i][j][1] = (max(dp[i-1][j][1], dp[i][j-1][1]) * v) % MOD
This breaks the algorithm because modulo changes the actual values, making comparisons invalid. For example, if one path gives 10^9 + 8 and another gives 3, after modulo they become 1 and 3 respectively, incorrectly suggesting the second path is better.
Solution: Only apply modulo at the very end, after finding the maximum product:
# CORRECT max_product = dp[-1][-1][1] return -1 if max_product < 0 else max_product % MOD
3. Integer Overflow in Languages with Fixed Integer Size
Pitfall: In languages like Java or C++, multiplying large numbers can cause integer overflow. The product can grow exponentially with grid size, easily exceeding the bounds of standard integer types.
Solution: Use appropriate data types:
- In Python: No issue as integers have arbitrary precision
- In Java: Use
longorBigInteger - In C++: Use
long longor implement custom handling
4. Incorrect Sign Flip Logic for Negative Numbers
Pitfall: When encountering a negative number, forgetting to swap min/max logic:
# WRONG - Using same logic as positive numbers
if v < 0:
dp[i][j][0] = min(dp[i-1][j][0], dp[i][j-1][0]) * v # Wrong!
dp[i][j][1] = max(dp[i-1][j][1], dp[i][j-1][1]) * v # Wrong!
Solution: Remember that multiplying by a negative number flips the ordering:
# CORRECT
if v < 0:
dp[i][j][0] = max(dp[i-1][j][1], dp[i][j-1][1]) * v # Max becomes min
dp[i][j][1] = min(dp[i-1][j][0], dp[i][j-1][0]) * v # Min becomes max
5. Not Tracking Both Minimum and Maximum
Pitfall: Only tracking the maximum product, thinking it's sufficient:
# WRONG - Only tracking maximum
dp[i][j] = max(dp[i-1][j], dp[i][j-1]) * grid[i][j]
This fails when negative numbers are involved because a very negative number (minimum) can become very positive (maximum) when multiplied by another negative number.
Solution: Always track both minimum and maximum at each cell to handle all possible sign combinations correctly.
Which of the following is a good use case for backtracking?
Recommended Readings
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
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion If you prefer videos here's a video that explains recursion in a fun and easy way Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask him
Want a Structured Path to Master System Design Too? Don’t Miss This!