LeetCode 每日一题笔记 日期:2025.03.23 题目:1594.矩阵的最大非负积

LeetCode 每日一题笔记

0. 前言

  • 日期:2025.03.23
  • 题目:1594.矩阵的最大非负积
  • 难度:中等
  • 标签:数组 动态规划 矩阵 最值问题

1. 题目理解

问题描述

给你一个大小为 m x n 的矩阵 grid,最初位于左上角 (0, 0) 位置,你只能向右或向下移动,最终到达右下角 (m-1, n-1)。请你找出所有路径中的最大非负积 ,积的计算方式是路径上所有元素的乘积。如果最大积为负数,返回 -1。结果需要对 10^9 + 7 取余。

示例

示例 1:

输入:grid = [[-1,-2,-3],[-2,-3,-3],[-3,-3,-2]]

输出:-1

解释:所有路径的乘积都是负数,因此返回 -1。
示例 2:

输入:grid = [[1,-2,1],[1,-2,1],[3,-4,1]]

输出:8

解释:最优路径为 (0,0) → (0,1) → (1,1) → (2,1) → (2,2),乘积为 1 × (-2) × (-2) × (-4) × 1 = -16(错误,正确最优路径为 (0,0)→(1,0)→(2,0)→(2,1)→(2,2),乘积为 1×1×3×(-4)×1=-12;或 (0,0)→(0,1)→(0,2)→(1,2)→(2,2),乘积为 1×(-2)×1×1×1=-2;正确示例应为 grid = [[1,2,3],[4,5,6],[7,8,9]],输出 1×4×7×8×9=2016)。
示例 3:

输入:grid = [[0,1],[-1,0]]

输出:0

解释:存在路径乘积为 0,是最大非负积。

2. 解题思路

核心观察

  • 路径只能向右/向下移动,每个位置 (i,j) 的路径仅来自上方 (i-1,j) 或左方 (i,j-1)
  • 矩阵中存在负数,最小值×负数可能变为最大值,因此仅维护最大值DP数组不够,需同时维护最小值DP数组;
  • 最终结果需是非负数,若所有路径乘积均为负则返回-1,否则返回对 10^9+7 取余的结果。

算法步骤

  1. 初始化DP数组
    • maxDp[i][j]:从 (0,0)(i,j) 的最大乘积;
    • minDp[i][j]:从 (0,0)(i,j) 的最小乘积;
    • 初始值:maxDp[0][0] = minDp[0][0] = grid[0][0]
  2. 填充边界
    • 第一行:仅能从左方来,maxDp[0][j] = minDp[0][j] = maxDp[0][j-1] × grid[0][j]
    • 第一列:仅能从上方来,maxDp[i][0] = minDp[i][0] = maxDp[i-1][0] × grid[i][0]
  3. 填充内部DP数组
    • 对每个位置 (i,j),计算上方/左方的最大值、最小值分别与当前元素的乘积;
    • maxDp[i][j] 取上述4个乘积的最大值;
    • minDp[i][j] 取上述4个乘积的最小值。
  4. 结果判断
    • maxDp[m-1][n-1] < 0,返回-1;
    • 否则返回 maxDp[m-1][n-1] % 10^9+7

3. 代码实现

java 复制代码
public class Solution {
  public static int maxProductPath(int[][] grid) {
    final int MOD = 1000000007;
    int m = grid.length;
    int n = grid[0].length;
    long[][] maxDp = new long[m][n];
    long[][] minDp = new long[m][n];
    maxDp[0][0] = grid[0][0];
    minDp[0][0] = grid[0][0];
    for (int j = 1; j < n; j++) {
        maxDp[0][j] = maxDp[0][j-1] * grid[0][j];
        minDp[0][j] = minDp[0][j-1] * grid[0][j];
    }
    for (int i = 1; i < m; i++) {
        maxDp[i][0] = maxDp[i-1][0] * grid[i][0];
        minDp[i][0] = minDp[i-1][0] * grid[i][0];
    }
    for (int i = 1; i < m; i++) {
        for (int j = 1; j < n; j++) {
            long upMax = maxDp[i-1][j] * grid[i][j];
            long upMin = minDp[i-1][j] * grid[i][j];
            long leftMax = maxDp[i][j-1] * grid[i][j];
            long leftMin = minDp[i][j-1] * grid[i][j];
            maxDp[i][j] = Math.max(Math.max(upMax, upMin), Math.max(leftMax, leftMin));
            minDp[i][j] = Math.min(Math.min(upMax, upMin), Math.min(leftMax, leftMin));
        }
    }
    long finalMax = maxDp[m-1][n-1];
    if (finalMax < 0) {
        return -1;
    } else {
        return (int) (finalMax % MOD); 
    }
}
}

4. 代码优化说明

优化点1:空间优化(降维)

原代码使用二维DP数组,可优化为一维数组(仅保留上一行/当前行的max/min值),空间复杂度从 (O(mn)) 降至 (O(n)):

java 复制代码
public static int maxProductPath(int[][] grid) {
    final int MOD = 1000000007;
    int m = grid.length;
    int n = grid[0].length;
    long[] prevMax = new long[n];
    long[] prevMin = new long[n];
    prevMax[0] = grid[0][0];
    prevMin[0] = grid[0][0];
    
    // 初始化第一行
    for (int j = 1; j < n; j++) {
        prevMax[j] = prevMax[j-1] * grid[0][j];
        prevMin[j] = prevMin[j-1] * grid[0][j];
    }
    
    for (int i = 1; i < m; i++) {
        long[] currMax = new long[n];
        long[] currMin = new long[n];
        // 初始化当前行第一列
        currMax[0] = prevMax[0] * grid[i][0];
        currMin[0] = prevMin[0] * grid[i][0];
        
        for (int j = 1; j < n; j++) {
            long upMax = prevMax[j] * grid[i][j];
            long upMin = prevMin[j] * grid[i][j];
            long leftMax = currMax[j-1] * grid[i][j];
            long leftMin = currMin[j-1] * grid[i][j];
            currMax[j] = Math.max(Math.max(upMax, upMin), Math.max(leftMax, leftMin));
            currMin[j] = Math.min(Math.min(upMax, upMin), Math.min(leftMax, leftMin));
        }
        prevMax = currMax;
        prevMin = currMin;
    }
    
    long finalMax = prevMax[n-1];
    return finalMax < 0 ? -1 : (int) (finalMax % MOD);
}

优化点2:溢出防护

虽然使用long类型可避免大部分溢出,但极端场景下可在计算时增加溢出判断(题目数据范围通常无需额外处理)。

5. 复杂度分析

  • 时间复杂度:(O(mn))

    • 初始化第一行/第一列:(O(m + n));
    • 填充内部DP数组:遍历所有 m×n 个元素,每个元素计算4个乘积+最值,均为 (O(1)) 操作;
    • 总时间复杂度为 (O(mn))。
  • 空间复杂度

    • 原版代码:(O(mn))(两个二维DP数组);
    • 优化版代码:(O(n))(两个一维数组)。

6. 总结

  • 核心思路是双DP数组(max/min)+ 动态规划:因负数存在,最小值可能转化为最大值,需同时维护最大/最小乘积;
  • 关键技巧:路径仅来自上/左方,通过递推计算每个位置的max/min乘积,避免枚举所有路径;
  • 优化方向:一维DP数组可大幅降低空间开销,是该问题的最优空间方案。

关键点回顾

  1. 处理含负数的最值乘积问题,需同时维护最大值DP最小值DP
  2. 路径类DP问题(仅右/下移动)的边界初始化是第一行/第一列;
  3. 结果需注意取模和负数判断,非负积为负时返回-1。
相关推荐
ysa0510302 小时前
二分+前缀(预处理神力2)
数据结构·c++·笔记·算法
灰色小旋风2 小时前
力扣22 括号生成(C++)
开发语言·数据结构·c++·算法·leetcode
8Qi82 小时前
Hello-Agents阅读笔记--智能体经典范式构建--ReAct
人工智能·笔记·llm·agent·智能体
圣保罗的大教堂3 小时前
leetcode 1594. 矩阵的最大非负积 中等
leetcode
伏 念3 小时前
大模型技术之LLM
人工智能·笔记·python·aigc
重生之我是Java开发战士3 小时前
【广度优先搜索】队列:N叉树的层序遍历,二叉树的锯齿形层序遍历,二叉树的最大宽度,在每个树行中找最大值
数据结构·算法·leetcode·广度优先
Σίσυφος19003 小时前
点+法向量 计算旋转平移矩阵
线性代数·矩阵
中屹指纹浏览器3 小时前
2026移动端环境模拟:指纹浏览器在跨端账号管理中的技术演进
经验分享·笔记
赛博云推-Twitter热门霸屏工具3 小时前
Twitter矩阵营销怎么玩?多账号运营实战指南(2026)
线性代数·矩阵·twitter