题目
给你一个下标从 0 开始的整数矩阵 grid 和一个整数 k。
返回包含 grid 左上角元素、元素和小于或等于 k 的 子矩阵的数目。
示例 1:

输入:grid = [[7,6,3],[6,6,1]], k = 18 输出:4 解释:如上图所示,只有 4 个子矩阵满足:包含 grid 的左上角元素,并且元素和小于或等于 18 。
示例 2:

输入:grid = [[7,2,9],[1,5,0],[2,6,6]], k = 20 输出:6 解释:如上图所示,只有 6 个子矩阵满足:包含 grid 的左上角元素,并且元素和小于或等于 20 。
提示:
m == grid.lengthn == grid[i].length1 <= n, m <= 10000 <= grid[i][j] <= 10001 <= k <= 10^9
解题思路
这道题的核心是二维前缀和(2D Prefix Sum)与容斥原理的应用。
题目要求统计所有以 $(0, 0)$ 为左上角、且元素总和小于或等于 $k$ 的子矩阵数量。如果对每个子矩阵都重新遍历求和,时间复杂度会非常高。二维前缀和的作用是通过预处理,在 $O(1)$ 的时间内得出任意左上角固定子矩阵的元素总和。
以下是具体的解题逻辑:
1. 状态定义
定义一个二维数组 $dp$,其中 $dp[i+1][j+1]$ 表示原矩阵 grid 中以 $(0, 0)$ 为左上角、以 $(i, j)$ 为右下角的子矩阵的元素总和。
2. 状态转移方程
为了计算当前子矩阵的总和,我们需要利用已计算的相邻子矩阵的结果,避免重复计算。根据容斥原理,状态转移方程为:
$$dp[i+1][j+1] = grid[i][j] + dp[i][j+1] + dp[i+1][j] - dp[i][j]$$
公式项解析:
- $grid[i][j]$:原矩阵当前坐标的元素值。
- $dp[i][j+1]$:当前元素上方相邻的子矩阵总和。
- $dp[i+1][j]$:当前元素左侧相邻的子矩阵总和。
- $- dp[i][j]$:上方和左侧子矩阵的交集部分。因为在相加时该区域被计算了两次,所以需要减去一次。
3. 边界条件处理
原矩阵 grid 的尺寸为 $m \times n$。在计算 $i = 0$ 或 $j = 0$(即原矩阵的第一行或第一列)时,公式中的 $i-1$ 或 $j-1$ 会导致数组下标越界。
为了处理这个问题,初始化一个尺寸为 $(m+1) \times (n+1)$ 的 $dp$ 数组,并将所有元素默认置为 0。
- $dp$ 数组的第 0 行和第 0 列作为计算时的基准零值,不对应
grid中的实际元素。 grid中的坐标 $(i, j)$ 严格映射到 $dp$ 数组中的 $(i+1, j+1)$。
4. 单调性与剪枝
题目给定 $grid[i][j] \ge 0$。根据这一条件,随着列索引 $j$ 的增加,子矩阵的面积扩大,其元素总和 $dp[i+1][j+1]$ 是单调非递减的。
- 在遍历第 $i$ 行时,如果判断出 $dp[i+1][j+1] > k$,则该行后续的所有列 $(j+2, j+3, \dots)$ 对应的前缀和必然也大于 $k$。
- 此时可以直接中断当前行的内层循环(
break),跳至下一行继续计算,从而减少冗余的计算步骤。
具体代码
class Solution:
def countSubmatrices(self, grid: List[List[int]], k: int) -> int:
m = len(grid[0])
n = len(grid)
dp = [[0] * (m + 1) for _ in range(n + 1)]
ans = 0
for i in range(1, n+1):
for j in range(1, m+1):
dp[i][j] = grid[i-1][j-1] + dp[i-1][j] + dp[i][j-1] - dp[i-1][j-1]
if dp[i][j] <= k:
ans += 1
return ans
impl Solution {
pub fn count_submatrices(grid: Vec<Vec<i32>>, k: i32) -> i32 {
let m = grid.len();
let n = grid[0].len();
let mut dp = vec![vec![0; n + 1]; m + 1];
let mut ans = 0;
for i in 1..=m {
for j in 1..=n {
dp[i][j] = grid[i-1][j-1] + dp[i-1][j] + dp[i][j-1] - dp[i-1][j-1];
if dp[i][j] > k {
break;
} else {
ans += 1;
}
}
}
ans
}
}