LeetCode 1594.矩阵中最大的非负乘积

LeetCode 1594.矩阵中最大的非负乘积(动态规划 + 滚动数组)

题目描述

给你一个大小为 m x n 的整数矩阵 grid。从 (0,0) 出发,每次只能向右或向下移动,到达 (m-1, n-1)。路径的乘积定义为路径上所有数字的乘积。请你返回路径乘积的最大值 。如果最大值为负数,则返回 -1。由于答案可能很大,需要对 10^9 + 7 取模。

算法思路

这道题与常规的"最大路径和"不同,因为乘法运算中负数乘负数会变成正数,所以仅仅记录最大值是不够的。我们需要同时记录最大值最小值,以便在遇到负数时能够通过"负负得正"得到更大的结果。

动态规划定义

我们采用滚动数组优化空间,只维护当前行的最小值和最大值:

  • f_min[j]:表示从起点到达当前行第 j 列的所有路径中,乘积的最小值。
  • f_max[j]:表示从起点到达当前行第 j 列的所有路径中,乘积的最大值。

遍历每个格子 (i, j),对于当前位置的值 x,其可能的前驱位置只有上方 (i-1, j) 和左方 (i, j-1)。我们利用这两个位置已经计算好的 minmax,来更新当前格子的 minmax

状态转移

mnmx 分别为前驱格子的最小值和最大值,则到达当前格子后,可能的新乘积为 mn * xmx * x。因此:

  • 当前格子的最小值 = min(所有前驱产生的 mn*x, mx*x)
  • 当前格子的最大值 = max(所有前驱产生的 mn*x, mx*x)

由于每一轮我们都只依赖上一行的数据和当前行已计算好的左边数据,因此可以用一维数组滚动更新。

边界条件

  • 起点 (0, 0)f_min[0] = f_max[0] = grid[0][0]
  • 其他格子若无法从上或左到达(即 i == 0j == 0),则只考虑可行的方向。

代码实现(C++)

cpp 复制代码
class Solution {
public:
    int maxProductPath(vector<vector<int>>& grid) {
        int m = grid.size(), n = grid[0].size();
        vector<long long> f_min(n), f_max(n);   // 滚动数组,存储当前行的最小/最大乘积

        for (int i = 0; i < m; ++i) {
            for (int j = 0; j < n; ++j) {
                long long x = grid[i][j];
                if (i == 0 && j == 0) {
                    f_min[0] = f_max[0] = x;
                    continue;
                }

                long long res_min = LLONG_MAX;
                long long res_max = LLONG_MIN;

                // 从上方转移
                if (i > 0) {
                    long long mn = f_min[j], mx = f_max[j];
                    res_min = min(mn * x, mx * x);
                    res_max = max(mn * x, mx * x);
                }

                // 从左方转移
                if (j > 0) {
                    long long mn = f_min[j - 1], mx = f_max[j - 1];
                    res_min = min(res_min, min(mn * x, mx * x));
                    res_max = max(res_max, max(mn * x, mx * x));
                }

                f_min[j] = res_min;
                f_max[j] = res_max;
            }
        }

        long long ans = f_max[n - 1], MOD = 1e9 + 7;
        return ans < 0 ? -1 : ans % MOD;
    }
};

复杂度分析

  • 时间复杂度:O(m × n),遍历整个矩阵一次。
  • 空间复杂度:O(n),使用两个长度为 n 的数组滚动更新。

关键点总结

  1. 负数乘法:必须同时维护最小值和最大值,因为最小值(负数)乘负数可能变为最大值。
  2. 滚动数组:只用一维数组记录当前行的结果,避免二维数组占用过多空间。
  3. 取模与负数判断 :最终结果若为负数返回 -1,否则对 10^9+7 取模。

示例验证

grid = [[1, -2], [3, 4]] 为例:

  • (0,0) 开始,f_min[0] = f_max[0] = 1
  • 处理 (0,1),只能从左边来:mn=1, mx=1, x=-2,乘积为 -2,所以 f_min[1] = -2, f_max[1] = -2
  • 处理 (1,0),只能从上边来:mn=1, mx=1, x=3,乘积为 3,所以 f_min[0] = 3, f_max[0] = 3
  • 处理 (1,1),可从上方 (0,1) 和左方 (1,0) 转移:
    • 从上:mn=-2, mx=-2, x=4,乘积为 -8
    • 从左:mn=3, mx=3, x=4,乘积为 12
    • 最小值 min(-8,12) = -8,最大值 max(-8,12) = 12
  • 最终答案 ans = 12,返回 12 % MOD = 12
相关推荐
圣光SG2 小时前
Java操作题练习(七)
java·开发语言·算法
Cx330_FCQ3 小时前
Tmux使用
服务器·git·算法
拳里剑气4 小时前
C++算法:多源BFS
c++·算法·宽度优先·多源bfs
ysa0510305 小时前
【板子】短序列dp(换成维护更小常数维度的dp)
c++·笔记·算法·板子
shwill1235 小时前
PID 算法(三)--- 增量 PID ↔ 单神经元 PID 等价映射
linux·算法
wabs6667 小时前
关于图论【卡码网110.字符串迁移的思考】
数据结构·算法·图论
hanlin037 小时前
刷题笔记:力扣第242、349题(哈希表)
笔记·算法·leetcode
浮沉9878 小时前
二分查找算法例题(二)
算法
only-qi8 小时前
大模型Agent面试攻略:落地工程痛点、评估体系与Agentic RAG核心精讲
人工智能·算法·面试·职场和发展·langchain·rag
Gauss松鼠会10 小时前
【GaussDB】GaussDB锁阻塞源头查询
java·开发语言·前端·数据库·算法·gaussdb·经验总结