1. 问题背景与理解
今天我们来拆解一道有趣的LeetCode矩阵问题——1582题"二进制矩阵中的特殊位置"。这道题看似简单,但其中蕴含着对二维数组遍历和条件判断的巧妙运用。我们先来看看题目描述:
给定一个大小为m x n的二进制矩阵mat,其中1代表特殊位置,0代表普通位置。定义"特殊位置"为:该位置值为1,并且所在行和列的所有其他元素都为0。要求返回矩阵中特殊位置的数量。
举个例子:
code复制输入:mat = [[1,0,0],
[0,0,1],
[1,0,0]]
输出:2
解释:(0,0)和(1,2)是特殊位置,因为它们的行和列其他元素都是0。
2. 解题思路分析
2.1 暴力解法
最直观的想法是遍历矩阵中的每个元素,当遇到1时,检查其所在行和列是否满足条件。这种方法的时间复杂度是O(m×n×(m+n)),因为对于每个元素,最坏情况下需要遍历整行和整列。
python复制def numSpecial(mat):
m, n = len(mat), len(mat[0])
count = 0
for i in range(m):
for j in range(n):
if mat[i][j] == 1:
# 检查行
row_valid = True
for k in range(n):
if k != j and mat[i][k] != 0:
row_valid = False
break
# 检查列
col_valid = True
for k in range(m):
if k != i and mat[k][j] != 0:
col_valid = False
break
if row_valid and col_valid:
count += 1
return count
2.2 优化思路
暴力解法虽然直观,但效率不高。我们可以通过预处理来优化:
- 预先计算每行和每列的1的个数
- 对于每个1的位置,如果所在行的1的总数为1且所在列的1的总数也为1,则该位置是特殊位置
这种方法将时间复杂度降低到O(m×n),因为我们只需要两次遍历矩阵(一次计算行和列的1的个数,一次检查特殊位置)。
python复制def numSpecial(mat):
m, n = len(mat), len(mat[0])
row_counts = [0] * m
col_counts = [0] * n
# 计算每行和每列的1的个数
for i in range(m):
for j in range(n):
if mat[i][j] == 1:
row_counts[i] += 1
col_counts[j] += 1
count = 0
for i in range(m):
for j in range(n):
if mat[i][j] == 1 and row_counts[i] == 1 and col_counts[j] == 1:
count += 1
return count
3. 代码实现详解
3.1 预处理阶段
预处理阶段我们需要计算两个数组:
row_counts[i]:第i行中1的个数col_counts[j]:第j列中1的个数
这两个数组可以通过一次遍历矩阵来填充:
python复制row_counts = [0] * m
col_counts = [0] * n
for i in range(m):
for j in range(n):
if mat[i][j] == 1:
row_counts[i] += 1
col_counts[j] += 1
3.2 检查特殊位置
有了预处理数据后,我们再次遍历矩阵,对于每个1的位置,只需检查:
row_counts[i] == 1col_counts[j] == 1
如果两个条件都满足,则该位置是特殊位置。
python复制count = 0
for i in range(m):
for j in range(n):
if mat[i][j] == 1 and row_counts[i] == 1 and col_counts[j] == 1:
count += 1
return count
4. 复杂度分析
- 时间复杂度:O(m×n),因为我们进行了两次完整的矩阵遍历
- 空间复杂度:O(m+n),用于存储行和列的计数数组
5. 边界条件与测试用例
5.1 常见测试用例
-
空矩阵:
python复制mat = [] # 应返回0 -
全0矩阵:
python复制mat = [[0,0],[0,0]] # 应返回0 -
全1矩阵:
python复制mat = [[1,1],[1,1]] # 应返回0 -
单个特殊位置:
python复制mat = [[1,0],[0,0]] # 应返回1
5.2 特殊情况的处理
-
矩阵维度为1×1时:
[[0]]→ 0[[1]]→ 1(因为行和列没有其他元素,默认满足条件)
-
多个特殊位置在同一行或同一列时:
python复制mat = [[1,0],[1,0]] # 应返回0,因为两个1在同一列
6. 优化与变种
6.1 空间优化
如果允许修改原矩阵,我们可以利用矩阵本身来存储部分信息,进一步减少空间使用。例如,可以用第一行和第一列来存储计数信息,但需要注意处理重叠部分。
6.2 并行计算
对于非常大的矩阵,可以考虑并行计算行和列的计数,利用多线程或GPU加速。
6.3 相关题目变种
- 定义"弱特殊位置":所在行或列至少有一个方向满足条件(而不是必须都满足)
- 计算所有特殊位置的和
- 找出最大的特殊位置区域(连续的1组成的区域)
7. 实际应用场景
这类矩阵问题在实际中有多种应用:
- 图像处理:识别图像中的孤立亮点
- 社交网络分析:找出网络中唯一的连接关系
- 数据库查询优化:识别稀疏矩阵中的特殊元素
- 游戏开发:棋盘类游戏中特殊位置的检测
8. 编码技巧与注意事项
- 边界检查:始终考虑矩阵为空或维度为1的情况
- 变量命名:使用有意义的变量名如row_counts和col_counts,提高代码可读性
- 提前终止:在暴力解法中,一旦发现行或列不满足条件,可以立即终止检查
- 测试驱动:先编写测试用例再实现代码,确保覆盖所有边界情况
9. 不同语言的实现差异
虽然算法逻辑相同,但不同语言的实现有些差异:
9.1 Java实现
java复制public int numSpecial(int[][] mat) {
int m = mat.length, n = mat[0].length;
int[] row = new int[m];
int[] col = new int[n];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (mat[i][j] == 1) {
row[i]++;
col[j]++;
}
}
}
int res = 0;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (mat[i][j] == 1 && row[i] == 1 && col[j] == 1) {
res++;
}
}
}
return res;
}
9.2 C++实现
cpp复制int numSpecial(vector<vector<int>>& mat) {
int m = mat.size(), n = mat[0].size();
vector<int> row(m, 0), col(n, 0);
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (mat[i][j] == 1) {
row[i]++;
col[j]++;
}
}
}
int res = 0;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (mat[i][j] == 1 && row[i] == 1 && col[j] == 1) {
res++;
}
}
}
return res;
}
10. 总结与个人心得
这道题目很好地展示了如何通过预处理来优化算法性能。在实际编程中,这种"空间换时间"的策略非常常见。我的几点体会:
- 不要急于编码:先充分理解问题,寻找规律和优化点
- 预处理是利器:很多矩阵问题都可以通过预处理行和列的统计信息来优化
- 测试很重要:特别是边界条件,往往能发现算法中的漏洞
- 可读性优先:虽然一行代码可能很酷,但清晰的代码更易于维护和调试
最后,对于矩阵问题,我建议多练习各种遍历方式和统计技巧,这些都是解决更复杂问题的基础。