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的特性
- 只计算非零元素的乘积
- 使用哈希表存储非零元素
- 优化内存使用
具体步骤:
- 找出矩阵A和B中的非零元素
- 只对非零元素进行乘法运算
- 累加结果到对应位置
- 返回最终结果矩阵
图解思路
矩阵乘法优化分析表
| 优化方式 | 实现方法 | 时间复杂度 | 空间复杂度 |
|---|---|---|---|
| 传统方法 | 三重循环 | 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 | 执行最快,内存占用最小 |
代码亮点
- 🎯 利用稀疏矩阵特性优化计算
- 💡 使用哈希表/压缩存储减少空间占用
- 🔍 双指针技术优化非零元素匹配
- 🎨 代码结构清晰,变量命名直观
常见错误分析
- 🚫 未考虑稀疏矩阵特性,使用传统矩阵乘法
- 🚫 存储结构选择不当,导致内存占用过大
- 🚫 未正确处理零元素,导致不必要的计算
- 🚫 矩阵维度判断错误
解法对比
| 解法 | 时间复杂度 | 空间复杂度 | 优点 | 缺点 |
|---|---|---|---|---|
| 传统方法 | O(mnk) | O(1) | 实现简单 | 效率低 |
| 稀疏优化 | O(nnz) | O(nnz) | 效率高 | 实现复杂 |
| 压缩存储 | O(nnz) | O(nnz) | 空间效率高 | 需要预处理 |
相关题目
- LeetCode 54. 螺旋矩阵 - 中等
- LeetCode 73. 矩阵置零 - 中等
- LeetCode 378. 有序矩阵中第K小的元素 - 中等
📖 系列导航
🔥 算法专题合集 - 查看完整合集
📢 关注合集更新:点击上方合集链接,关注获取最新题解!目前已更新第311题。
💬 互动交流
感谢大家耐心阅读到这里!希望这篇题解能够帮助你更好地理解和掌握这道算法题。
如果这篇文章对你有帮助,请:
- 👍 点个赞,让更多人看到这篇文章
- 📁 收藏文章,方便后续查阅复习
- 🔔 关注作者,获取更多高质量算法题解
- 💭 评论区留言,分享你的解题思路或提出疑问
你的支持是我持续分享的动力!
💡 一起进步:算法学习路上不孤单,欢迎一起交流学习!