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
相关推荐
We་ct2 小时前
LeetCode 162. 寻找峰值:二分高效求解
前端·算法·leetcode·typescript·二分·暴力
丶小鱼丶2 小时前
数据结构和算法之【二叉树】
java·数据结构·算法
hanlin032 小时前
刷题笔记:力扣第38题-外观数列
算法·leetcode
2301_793804692 小时前
模板代码安全性增强
开发语言·c++·算法
测试_AI_一辰2 小时前
Agent & RAG 测试工程笔记 13:RAG检索层原理拆解:从“看不懂”到手算召回过程
人工智能·笔记·功能测试·算法·ai·ai编程
干啥啥不行,秃头第一名2 小时前
C++中的观察者模式
开发语言·c++·算法
阿Y加油吧2 小时前
力扣打卡——反转链表、回文链表判断 题解
算法·leetcode
羊小猪~~2 小时前
算法/力扣--数组典型题目
c语言·c++·python·算法·leetcode·职场和发展·求职招聘
Johnny.Cheung2 小时前
【德国技术面试】两道小算法题(求两数之和/解谜游戏)
算法·面试