Article / 文章

LeetCode 第311题:稀疏矩阵的乘法

给定两个 稀疏矩阵 A 和 B,请你返回 AB 的结果。你可以默认 A 的列数等于 B 的行数。 请仔细考虑你的实现方案,矩阵 A 和 B 可能非常大,而且稀疏(大部分元素为 0)。

📖 文章摘要

本文详细解析LeetCode第311题“稀疏矩阵的乘法”,这是一道矩阵运算优化题目。文章提供了基于稀疏矩阵特性的优化解法,包含C#、Python、C++三种语言实现,配有详细的算法分析和性能分析。适合对矩阵运算和性能优化感兴趣的程序员。

核心知识点: 矩阵乘法、稀疏矩阵优化、空间优化、哈希表
难度等级: 中等
推荐人群: 具有基础数学知识,想要提升算法优化能力的程序员

题目描述

给定两个 稀疏矩阵 A 和 B,请你返回 AB 的结果。你可以默认 A 的列数等于 B 的行数。

请仔细考虑你的实现方案,矩阵 A 和 B 可能非常大,而且稀疏(大部分元素为 0)。

示例

示例 1:

输入:
A = [[ 1, 0, 0],
     [-1, 0, 3]]
B = [[ 7, 0, 0],
     [ 0, 0, 0],
     [ 0, 0, 1]]
输出:
     [[ 7, 0, 0],
      [-7, 0, 3]]

示例 2:

输入:
A = [[0]]
B = [[0]]
输出:[[0]]

提示

  • 1 <= A.length, B.length <= 100
  • 1 <= A[i].length, B[i].length <= 100
  • -100 <= A[i][j], B[i][j] <= 100

解题思路

本题的关键是利用稀疏矩阵的特性进行优化,避免不必要的乘法运算。

关键点:

  • 利用稀疏矩阵中大量元素为0的特性
  • 只计算非零元素的乘积
  • 使用哈希表存储非零元素
  • 优化内存使用

具体步骤:

  1. 找出矩阵A和B中的非零元素
  2. 只对非零元素进行乘法运算
  3. 累加结果到对应位置
  4. 返回最终结果矩阵

图解思路

矩阵乘法优化分析表

优化方式 实现方法 时间复杂度 空间复杂度
传统方法 三重循环 O(n³) O(1)
稀疏优化 哈希表 O(k₁k₂n) O(k₁+k₂)
列压缩 压缩存储 O(nnz) O(nnz)

注:k₁、k₂为两个矩阵的非零元素个数,nnz为非零元素总数

计算过程分析表

步骤 操作 数据结构 说明
预处理 存储非零元素 哈希表/列表 记录位置和值
计算 乘加操作 矩阵 只计算必要的乘法
优化 跳过零元素 条件判断 减少无效计算

代码实现

C# 实现

public class Solution {
    public int[][] Multiply(int[][] A, int[][] B) {
        int m = A.Length, k = A[0].Length, n = B[0].Length;
        int[][] result = new int[m][];
        for (int i = 0; i < m; i++) {
            result[i] = new int[n];
        }
        
        // 优化1:存储A中的非零元素
        var nonZeroA = new Dictionary<int, List<(int col, int val)>>();
        for (int i = 0; i < m; i++) {
            nonZeroA[i] = new List<(int, int)>();
            for (int j = 0; j < k; j++) {
                if (A[i][j] != 0) {
                    nonZeroA[i].Add((j, A[i][j]));
                }
            }
        }
        
        // 优化2:存储B中的非零元素
        var nonZeroB = new Dictionary<int, List<(int row, int val)>>();
        for (int j = 0; j < n; j++) {
            nonZeroB[j] = new List<(int, int)>();
            for (int i = 0; i < k; i++) {
                if (B[i][j] != 0) {
                    nonZeroB[j].Add((i, B[i][j]));
                }
            }
        }
        
        // 计算乘积
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                int sum = 0;
                foreach (var (col, valA) in nonZeroA[i]) {
                    foreach (var (row, valB) in nonZeroB[j]) {
                        if (col == row) {
                            sum += valA * valB;
                        }
                    }
                }
                result[i][j] = sum;
            }
        }
        
        return result;
    }
}

Python 实现

class Solution:
    def multiply(self, A: List[List[int]], B: List[List[int]]) -> List[List[int]]:
        m, k, n = len(A), len(A[0]), len(B[0])
        result = [[0] * n for _ in range(m)]
        
        # 优化:只存储非零元素
        sparse_A = {}  # 按行存储非零元素
        for i in range(m):
            for j in range(k):
                if A[i][j] != 0:
                    if i not in sparse_A:
                        sparse_A[i] = []
                    sparse_A[i].append((j, A[i][j]))
        
        sparse_B = {}  # 按列存储非零元素
        for j in range(n):
            for i in range(k):
                if B[i][j] != 0:
                    if j not in sparse_B:
                        sparse_B[j] = []
                    sparse_B[j].append((i, B[i][j]))
        
        # 计算乘积
        for i in range(m):
            if i not in sparse_A:
                continue
            for j in range(n):
                if j not in sparse_B:
                    continue
                # 使用双指针优化
                p1, p2 = 0, 0
                sum_val = 0
                while p1 < len(sparse_A[i]) and p2 < len(sparse_B[j]):
                    col_a, val_a = sparse_A[i][p1]
                    row_b, val_b = sparse_B[j][p2]
                    if col_a == row_b:
                        sum_val += val_a * val_b
                        p1 += 1
                        p2 += 1
                    elif col_a < row_b:
                        p1 += 1
                    else:
                        p2 += 1
                result[i][j] = sum_val
        
        return result

C++ 实现

class Solution {
public:
    vector<vector<int>> multiply(vector<vector<int>>& A, vector<vector<int>>& B) {
        int m = A.size(), k = A[0].size(), n = B[0].size();
        vector<vector<int>> result(m, vector<int>(n, 0));
        
        // 压缩存储A的非零元素
        vector<vector<pair<int, int>>> sparseA(m);
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < k; j++) {
                if (A[i][j] != 0) {
                    sparseA[i].emplace_back(j, A[i][j]);
                }
            }
        }
        
        // 压缩存储B的非零元素
        vector<vector<pair<int, int>>> sparseB(n);
        for (int j = 0; j < n; j++) {
            for (int i = 0; i < k; i++) {
                if (B[i][j] != 0) {
                    sparseB[j].emplace_back(i, B[i][j]);
                }
            }
        }
        
        // 计算乘积
        for (int i = 0; i < m; i++) {
            if (sparseA[i].empty()) continue;
            
            for (int j = 0; j < n; j++) {
                if (sparseB[j].empty()) continue;
                
                int sum = 0;
                int p1 = 0, p2 = 0;
                while (p1 < sparseA[i].size() && p2 < sparseB[j].size()) {
                    if (sparseA[i][p1].first == sparseB[j][p2].first) {
                        sum += sparseA[i][p1].second * sparseB[j][p2].second;
                        p1++;
                        p2++;
                    } else if (sparseA[i][p1].first < sparseB[j][p2].first) {
                        p1++;
                    } else {
                        p2++;
                    }
                }
                result[i][j] = sum;
            }
        }
        
        return result;
    }
};

执行结果

C# 实现

  • 执行用时:96 ms
  • 内存消耗:42.8 MB

Python 实现

  • 执行用时:72 ms
  • 内存消耗:16.9 MB

C++ 实现

  • 执行用时:8 ms
  • 内存消耗:11.2 MB

性能对比

语言 执行用时 内存消耗 特点
C# 96 ms 42.8 MB 性能适中,内存占用较大
Python 72 ms 16.9 MB 执行较快,内存占用适中
C++ 8 ms 11.2 MB 执行最快,内存占用最小

代码亮点

  1. 🎯 利用稀疏矩阵特性优化计算
  2. 💡 使用哈希表/压缩存储减少空间占用
  3. 🔍 双指针技术优化非零元素匹配
  4. 🎨 代码结构清晰,变量命名直观

常见错误分析

  1. 🚫 未考虑稀疏矩阵特性,使用传统矩阵乘法
  2. 🚫 存储结构选择不当,导致内存占用过大
  3. 🚫 未正确处理零元素,导致不必要的计算
  4. 🚫 矩阵维度判断错误

解法对比

解法 时间复杂度 空间复杂度 优点 缺点
传统方法 O(mnk) O(1) 实现简单 效率低
稀疏优化 O(nnz) O(nnz) 效率高 实现复杂
压缩存储 O(nnz) O(nnz) 空间效率高 需要预处理

相关题目

📖 系列导航

🔥 算法专题合集 - 查看完整合集

📢 关注合集更新:点击上方合集链接,关注获取最新题解!目前已更新第311题。

💬 互动交流

感谢大家耐心阅读到这里!希望这篇题解能够帮助你更好地理解和掌握这道算法题。

如果这篇文章对你有帮助,请:

  • 👍 点个赞,让更多人看到这篇文章
  • 📁 收藏文章,方便后续查阅复习
  • 🔔 关注作者,获取更多高质量算法题解
  • 💭 评论区留言,分享你的解题思路或提出疑问

你的支持是我持续分享的动力!

💡 一起进步:算法学习路上不孤单,欢迎一起交流学习!