beautifulremi / 1594. 矩阵的最大非负积

Created Mon, 23 Mar 2026 10:44:11 +0800 Modified Mon, 23 Mar 2026 05:26:54 +0000
1493 Words

题目

给你一个大小为 m x n 的矩阵 grid 。最初,你位于左上角 (0, 0) ,每一步,你可以在矩阵中 向右 或 向下 移动。

在从左上角 (0, 0) 开始到右下角 (m - 1, n - 1) 结束的所有路径中,找出具有 最大非负积 的路径。路径的积是沿路径访问的单元格中所有整数的乘积。

返回 最大非负积 对 109 + 7 取余 的结果。如果最大积为 负数 ,则返回 -1 。

**注意,**取余是在得到最大积之后执行的。

示例 1:

输入:grid = [[-1,-2,-3],[-2,-3,-3],[-3,-3,-2]] 输出:-1 解释:从 (0, 0) 到 (2, 2) 的路径中无法得到非负积,所以返回 -1 。

示例 2:

输入:grid = [[1,-2,1],[1,-2,1],[3,-4,1]] 输出:8 解释:最大非负积对应的路径如图所示 (1 * 1 * -2 * -4 * 1 = 8)

示例 3:

输入:grid = [[1,3],[0,-4]] 输出:0 解释:最大非负积对应的路径如图所示 (1 * 0 * -4 = 0)

提示:

  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n <= 15
  • -4 <= grid[i][j] <= 4

解题思路

在每一个格子 $(i, j)$,由于我们不知道后面会不会遇到负数,所以我们需要同时记录到达这里的两个极端:

  1. 最大乘积 ($dp_{max}[i, j]$):记录从起点到当前位置能得到的最大乘积。

  2. 最小乘积 ($dp_{min}[i, j]$):记录从起点到当前位置能得到的最小乘积(尤其是绝对值很大的负数)。


由于只能向右或向下移动,到达 $(i, j)$ 的路径只能来自上方 $(i-1, j)$ 或左方 $(i, j-1)$。

当你到达格子 grid[i][j] 时,设当前值为 $v$:

  • 如果 $v > 0$:

    • 最大积来自“之前的最大积 $\times v$”。

    • 最小积来自“之前的最小积 $\times v$”。

  • 如果 $v < 0$:

    • 最大积来自“之前的最小积 $\times v$”(负负得正,翻身做主人)。

    • 最小积来自“之前的最大积 $\times v$”(正负得负,变得更小)。

  • 如果 $v = 0$:

    • 最大积和最小积都是 $0$。

数学表达:

$$dp_{max}[i][j] = \max(dp_{max}[i-1][j], dp_{max}[i][j-1]) \cdot v \quad (\text{若 } v \ge 0)$$

$$dp_{max}[i][j] = \min(dp_{min}[i-1][j], dp_{min}[i][j-1]) \cdot v \quad (\text{若 } v < 0)$$


需要注意的:

  • 数据溢出:虽然矩阵只有 $15 \times 15$,且每个数最大只有 $4$,但路径长度最长是 $15+15-1 = 29$。最大乘积约为 $4^{29} = 2^{58}$。这个数值超过了 int32(最大约 $2 \cdot 10^9$),但能装进 int64。所以请务必使用 long long (C++) 或 int64 (Go/Rust)。

  • 先计算,后取余:题目要求先找最大积,最后再对 $10^9 + 7$ 取余。千万不要在中间过程中取余,否则会破坏负号和大小比较的逻辑(取模运算会丢失大小关系)。

  • 边界初始化

    • 起点 $(0,0)$ 的 $dp_{max}$ 和 $dp_{min}$ 都是 grid[0][0]

    • 第一行只能从左边来,第一列只能从上面来,需要单独处理循环。


算法复杂度

  • 时间复杂度:$O(m \times n)$,我们需要遍历整个矩阵一次。

  • 空间复杂度:$O(m \times n)$,用于存储两个 DP 矩阵(当然,如果你想进阶一下,可以用滚动数组优化到 $O(n)$)。

具体代码

func maxProductPath(grid [][]int) int {

    m := len(grid[0])
    n := len(grid)
    mod := int64(1e9 + 7)

    dp_max := make([][]int64, n)
    dp_min := make([][]int64, n)
    for i := range n {
        dp_max[i] = make([]int64, m)
        dp_min[i] = make([]int64, m)
    }

    dp_max[0][0] = int64(grid[0][0])
    dp_min[0][0] = int64(grid[0][0])

    // 第一行和列
    for i := 1; i < max(m, n); i++ {

        if i < m {
            dp_max[0][i] = dp_max[0][i - 1] * int64(grid[0][i])
            dp_min[0][i] = dp_max[0][i]
        }

        if i < n {
            dp_max[i][0] = dp_max[i - 1][0] * int64(grid[i][0])
            dp_min[i][0] = dp_max[i][0]
        }
    }

    for i := 1; i < n; i++ {
        for j := 1; j < m; j++ {
            val := int64(grid[i][j])
            if val > 0 {
                dp_max[i][j] = max(dp_max[i - 1][j] * val, dp_max[i][j - 1] * val)
                dp_min[i][j] = min(dp_min[i - 1][j] * val, dp_min[i][j - 1] * val)
            } else {
                dp_max[i][j] = max(dp_min[i - 1][j] * val, dp_min[i][j - 1] * val)
                dp_min[i][j] = min(dp_max[i - 1][j] * val, dp_max[i][j - 1] * val)
            }
        }
    }

    if dp_max[n - 1][m - 1] < 0 {
        return -1
    }
    return int(dp_max[n - 1][m - 1] % mod)
}
use std::cmp;

impl Solution {
    pub fn max_product_path(grid: Vec<Vec<i32>>) -> i32 {
        let n = grid.len();
    let m = grid[0].len();
    let mod_val: i64 = 1_000_000_007;

    // 1. 初始化 DP 数组:Rust 可以一行搞定二维向量的初始化
    // vec![初始值; 长度]
    let mut dp_max = vec![vec![0i64; m]; n];
    let mut dp_min = vec![vec![0i64; m]; n];

    // 2. 起点初始化
    dp_max[0][0] = grid[0][0] as i64;
    dp_min[0][0] = grid[0][0] as i64;

    // 3. 初始化第一行和第一列
    // 使用 std::cmp::max(m, n) 保持你之前的逻辑
    for i in 1..cmp::max(m, n) {
        if i < m {
            dp_max[0][i] = dp_max[0][i - 1] * grid[0][i] as i64;
            dp_min[0][i] = dp_max[0][i];
        }
        if i < n {
            dp_max[i][0] = dp_max[i - 1][0] * grid[i][0] as i64;
            dp_min[i][0] = dp_max[i][0];
        }
    }

    // 4. 动态规划填充
    for i in 1..n {
        for j in 1..m {
            let val = grid[i][j] as i64;
            if val >= 0 {
                // Rust 中比较两个数可以用 a.max(b)
                dp_max[i][j] = cmp::max(dp_max[i - 1][j], dp_max[i][j - 1]) * val;
                dp_min[i][j] = cmp::min(dp_min[i - 1][j], dp_min[i][j - 1]) * val;
            } else {
                // 负数:最大变最小,最小变最大
                dp_max[i][j] = cmp::min(dp_min[i - 1][j], dp_min[i][j - 1]) * val;
                dp_min[i][j] = cmp::max(dp_max[i - 1][j], dp_max[i][j - 1]) * val;
            }
        }
    }

    // 5. 结果处理
    let res = dp_max[n - 1][m - 1];
    if res < 0 {
        -1
    } else {
        (res % mod_val) as i32
    }
    }
}