LeetCode 每日一题笔记 日期:2025.03.24 题目:2906.构造乘积矩阵

LeetCode 每日一题笔记

0. 前言

  • 日期:2025.03.24
  • 题目:2906.构造乘积矩阵
  • 难度:中等
  • 标签:数组 矩阵 前缀和

1. 题目理解

问题描述

给你一个下标从 0 开始、大小为 n * m 的二维整数矩阵 grid,定义一个下标从 0 开始、大小为 n * m 的二维矩阵 p。如果满足以下条件,则称 pgrid乘积矩阵

  • 对于每个元素 p[i][j],它的值等于除了 grid[i][j] 外所有元素的乘积,且乘积对 12345 取余数。
    返回 grid 的乘积矩阵。

示例

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

输出:[[24,12],[8,6]]

解释:

  • p[0][0] = grid[0][1] * grid[1][0] * grid[1][1] = 2 * 3 * 4 = 24
  • p[0][1] = grid[0][0] * grid[1][0] * grid[1][1] = 1 * 3 * 4 = 12
  • p[1][0] = grid[0][0] * grid[0][1] * grid[1][1] = 1 * 2 * 4 = 8
  • p[1][1] = grid[0][0] * grid[0][1] * grid[1][0] = 1 * 2 * 3 = 6

2. 解题思路

核心观察

  • 暴力求解的问题 :若直接对每个元素 grid[i][j],遍历所有元素计算乘积(排除自身),时间复杂度为 O((n∗m)2)O((n*m)^2)O((n∗m)2)。当矩阵规模较大时(如 n、m 为 1000),计算量会达到 101210^{12}1012 级别,远超时间限制,且多次乘法会导致数值溢出(即使取模也无法降低时间复杂度)。
  • 前缀/后缀乘积优化 :将二维矩阵展平为一维数组,通过「前缀乘积数组」和「后缀乘积数组」快速计算每个位置排除自身的总乘积:
    • 前缀乘积 pre[i]:表示一维数组中前 i 个元素的乘积(包含 i);
    • 后缀乘积 suf[i]:表示一维数组中从 i 到末尾的乘积(包含 i);
    • 对于位置 k,排除自身的总乘积 = 前缀乘积 pre[k-1] * 后缀乘积 suf[k+1](边界位置单独处理)。

算法步骤

  1. 展平矩阵 :将二维矩阵 grid 转换为一维数组 arr,便于统一计算前缀/后缀乘积;
  2. 计算前缀乘积pre[0] = arr[0]pre[i] = (pre[i-1] * arr[i]) % 12345
  3. 计算后缀乘积suf[len-1] = arr[len-1]suf[i] = (suf[i+1] * arr[i]) % 12345
  4. 重构结果矩阵:遍历每个位置,根据前缀/后缀乘积计算排除自身的总乘积,转换回二维矩阵并取模。

3. 代码实现

java 复制代码
package com.sheeta1998.lec.lc2906;

class Solution {
    public int[][] constructProductMatrix(int[][] grid) {
        int n = grid.length;    // 矩阵行数
        int m = grid[0].length; // 矩阵列数
        int total = n * m;      // 一维数组总长度
        int[] arr = new int[total];
        int[] pre = new int[total]; // 前缀乘积数组
        int[] suf = new int[total]; // 后缀乘积数组
        
        // 步骤1:将二维矩阵展平为一维数组
        int idx = 0;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                arr[idx++] = grid[i][j];
            }
        }
        
        // 步骤2:计算前缀乘积
        pre[0] = arr[0] % 12345;
        for (int i = 1; i < total; i++) {
            pre[i] = (pre[i-1] * arr[i]) % 12345;
        }
        
        // 步骤3:计算后缀乘积
        suf[total-1] = arr[total-1] % 12345;
        for (int i = total-2; i >= 0; i--) {
            suf[i] = (suf[i+1] * arr[i]) % 12345;
        }
        
        // 步骤4:重构乘积矩阵
        idx = 0;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                if (idx == 0) {
                    // 第一个元素:只有后缀乘积
                    grid[i][j] = suf[1] % 12345;
                } else if (idx == total-1) {
                    // 最后一个元素:只有前缀乘积
                    grid[i][j] = pre[total-2] % 12345;
                } else {
                    // 中间元素:前缀*后缀
                    grid[i][j] = (pre[idx-1] * suf[idx+1]) % 12345;
                }
                idx++;
            }
        }
        return grid;
    }
}

4. 代码优化说明

  1. 空间优化 :原代码中 prearr/sufarr 命名简化为 pre/suf,变量名更直观;
  2. 取模时机 :每一步乘法后立即对 12345 取模,避免整数溢出(Java 中 int 最大值约 2×1092×10^92×109,多次乘法易溢出);
  3. 边界处理:明确区分第一个/最后一个元素的计算逻辑,避免数组越界;
  4. 变量复用 :直接修改原矩阵 grid 存储结果,无需额外创建二维数组,节省空间。

5. 复杂度分析

  • 时间复杂度 :O(n×m)O(n×m)O(n×m)。展平矩阵、计算前缀/后缀乘积、重构矩阵均为一次遍历,总次数为 3×n×m3×n×m3×n×m,属于线性时间复杂度;
  • 空间复杂度 :O(n×m)O(n×m)O(n×m)。需要额外存储一维数组 arr、前缀数组 pre、后缀数组 suf,总空间为 3×n×m3×n×m3×n×m(可进一步优化为 O(1)O(1)O(1) 空间,直接在二维矩阵上计算前缀/后缀,但代码可读性降低)。

6. 总结

  1. 暴力求解不可行的原因 :时间复杂度为 O((n×m)2)O((n×m)^2)O((n×m)2),矩阵规模较大时会超时,且多次乘法易导致数值溢出;
  2. 核心优化思路:利用「前缀/后缀乘积」将时间复杂度降至线性,通过展平二维矩阵简化计算逻辑;
  3. 关键细节:每一步乘法后取模避免溢出,边界位置单独处理防止数组越界。

关键点回顾

  1. 暴力解法因 O((n∗m)2)O((n*m)^2)O((n∗m)2) 时间复杂度无法通过大规模用例,必须用前缀/后缀乘积优化;
  2. 展平二维矩阵是简化前缀/后缀计算的核心技巧,最终需还原为二维结果;
  3. 取模操作需在每一步乘法后执行,避免整数溢出且满足题目要求。
相关推荐
Engineer邓祥浩34 分钟前
JVM学习笔记(13) 第五部分 高效并发 第12章 Java内存模型与线程
jvm·笔记·学习
我命由我1234539 分钟前
程序员的心理学学习笔记 - 反刍思维
经验分享·笔记·学习·职场和发展·求职招聘·职场发展·学习方法
@BangBang2 小时前
leetcode (4): 连通域/岛屿问题
算法·leetcode·深度优先
Mr_pyx2 小时前
【LeetCode Hot 100】 除自身以外数组的乘积(238题)多解法详解
算法·leetcode·职场和发展
故事和你913 小时前
洛谷-数据结构-1-3-集合3
数据结构·c++·算法·leetcode·贪心算法·动态规划·图论
xuhaoyu_cpp_java3 小时前
事务学习(一)
数据库·经验分享·笔记·学习·mysql
ulias2123 小时前
leetcode热题 - 3
c++·算法·leetcode·职场和发展
代码地平线4 小时前
OpenCode零基础教程完整版
笔记
菜鸟丁小真4 小时前
LeetCode hot100-287.寻找重复数和994.腐烂的橘子
数据结构·算法·leetcode·知识点总结
.Cnn4 小时前
Ajax与Vue 生命周期核心笔记
前端·javascript·vue.js·笔记·ajax