带限制的最大子矩阵和(限制子矩阵面积至少为S)
字数 3660 2025-12-11 04:54:21
带限制的最大子矩阵和(限制子矩阵面积至少为S)
问题描述
给定一个 m × n 的整数矩阵 matrix 和一个整数 S,要求找出一个子矩阵,使得该子矩阵内所有元素的和最大,并且子矩阵的面积(即行数乘以列数)至少为 S。返回该最大和。
例如:
matrix = [
[1, 2, -1],
[3, 4, 5],
[-2, 0, 2]
]
S = 4
可能的子矩阵之一是整个矩阵(面积=9,和=1+2-1+3+4+5-2+0+2=14),但题目要求面积至少为4,因此允许取部分子矩阵。其中一个满足面积至少4且和最大的子矩阵是:
[3, 4]
[-2, 0]
面积=2×2=4≥S,和=3+4+(-2)+0=5。但这是最大吗?检查所有子矩阵后,最大和是取整个矩阵的和14,因为其面积9≥4,且和14最大,所以答案是14。
实际上,当S较小时,最大和往往对应面积较大的子矩阵(因为可以包含更多正数)。但如果矩阵中有很多负数,则需要在面积约束和元素和之间权衡。
题目本质:在二维矩阵中,找一个面积至少为S的连续子矩阵,使得元素和最大。
解题思路
这是一个二维动态规划(或者更准确说是二维前缀和 + 滑动窗口/枚举优化)的问题。
直接暴力枚举所有子矩阵需要 O(m²n²) 的时间(枚举左上角、右下角),在 m,n 较大时不可接受。我们需要更高效的方法。
核心步骤:
-
预处理二维前缀和
- 定义
prefix[i][j]表示原矩阵中从(0,0)到(i-1,j-1)的子矩阵和(i,j 从1开始计数)。 - 计算:
prefix[i][j] = matrix[i-1][j-1] + prefix[i-1][j] + prefix[i][j-1] - prefix[i-1][j-1]。 - 这样可以在 O(1) 时间内得到任意子矩阵的和:
子矩阵左上角(r1,c1),右下角(r2,c2)(0-based)的和为:sum = prefix[r2+1][c2+1] - prefix[r1][c2+1] - prefix[r2+1][c1] + prefix[r1][c1]
- 定义
-
将问题转化为一维问题
- 固定子矩阵的上下边界(行
r1到r2),将这一多行合并成“一维数组”:
我们可以用前缀和快速得到:colSum[c] = sum_{row=r1..r2} matrix[row][c]colSum[c] = prefix[r2+1][c+1] - prefix[r1][c+1] - prefix[r2+1][c] + prefix[r1][c](实际上可以直接用前缀和差值得到)。 - 问题变为:在这个一维数组
colSum[0..n-1]中,找一个长度至少为L的连续子数组,使得和最大,且L满足:子矩阵的列数≥ceil(S / (r2-r1+1))(因为面积=行数×列数≥S)。
- 固定子矩阵的上下边界(行
-
在一维数组中找长度至少为L的最大和子数组
- 设当前行数为
k = r2 - r1 + 1,则要求列数cols ≥ ceil(S / k)。 - 我们令
minCols = ceil(S / k)。 - 问题:在数组
A[0..n-1](即colSum)中,找长度至少为minCols的连续子数组的最大和。 - 这可以用 前缀和 + 维护最小前缀和 的方法解决:
- 计算前缀和
pref[j] = sum(A[0..j-1]),pref[0]=0。 - 遍历
j从minCols到n:- 当前子数组结尾于
j-1,长度为len(len ≥ minCols),其和为pref[j] - pref[j-len]。 - 对所有可能的 len≥minCols,我们只需维护
pref[j-minCols]之前的最小前缀和minPref(包括pref[0..j-minCols]),那么以 j 结尾的满足长度≥minCols 的最大子数组和为pref[j] - minPref。 - 更新
minPref = min(minPref, pref[j-minCols+1])注意下标。
正确实现时,我们让j从minCols到n,维护minPref = min(minPref, pref[j-minCols])即可。
- 当前子数组结尾于
- 计算前缀和
- 设当前行数为
-
枚举行边界并求最大值
- 枚举所有可能的
r1和r2(O(m²)),对每一对行,用 O(n) 时间求一维问题的解。 - 总复杂度 O(m² × n),在 m 和 n 约 200 时可行。
- 若 m>n,可以转置矩阵,保证 O(min(m,n)² × max(m,n))。
- 枚举所有可能的
详细算法步骤
- 输入矩阵
matrix(m 行 n 列)和整数 S。 - 计算二维前缀和
prefix,大小为(m+1)×(n+1)。 - 初始化答案
ans = -inf。 - 枚举上边界
r1从 0 到 m-1:- 枚举下边界
r2从 r1 到 m-1:- 行数
k = r2 - r1 + 1。 - 最小所需列数
minCols = ceil(S / k)。若minCols > n,则此 k 不可能满足面积≥S,跳过。 - 构造一维数组
colSum[0..n-1],其中colSum[c] = 从 r1 到 r2 行第 c 列的和,用二维前缀和公式快速计算。 - 计算一维数组
colSum的前缀和pref[0..n],pref[0]=0。 - 初始化
minPref = pref[0]。 - 对于
j从minCols到n:currentSum = pref[j] - minPref。- 更新
ans = max(ans, currentSum)。 - 更新
minPref = min(minPref, pref[j-minCols+1])(注意:j-minCols+1可能会超出范围吗?不,因为 j≥minCols,j-minCols+1≥1,且 j-minCols+1 ≤ n-minCols+1 ≤ n,但 pref 长度 n+1,所以安全)。
- 注意:当 minCols=0 时(S=0 时可能),但面积至少 S,S=0 相当于无面积限制,我们直接求最大子矩阵和即可(此时 minCols=0,一维问题退化为普通最大子数组和,用 Kadane 算法)。
- 行数
- 枚举下边界
- 返回 ans。
例子演示
例:
matrix = [[2, 1, -3],
[0, 6, 5]]
S = 3
- 前缀和:
prefix:
0 0 0 0
0 2 3 0
0 2 9 11
- 枚举:
- r1=0, r2=0: k=1, minCols=ceil(3/1)=3。
一维数组 colSum = [2, 1, -3]。
pref = [0, 2, 3, 0]。
j=3: currentSum=pref[3]-minPref=0-min(0,2,3)=0-0=0。
ans=0。
但长度刚好3,其实列数=3≥3,和=2+1-3=0。 - r1=0, r2=1: k=2, minCols=ceil(3/2)=2。
colSum = 第一列:2+0=2;第二列:1+6=7;第三列:-3+5=2 → [2,7,2]。
pref=[0,2,9,11]。
minPref=pref[0]=0。
j=2: currentSum=pref[2]-0=9,更新 ans=9。
minPref=min(0, pref[0])=0(j-minCols+1=2-2+1=1,pref[1]=2,但 minPref 还是 0)。
j=3: currentSum=pref[3]-0=11,更新 ans=11。
所以得到子矩阵是整个矩阵,面积=2×3=6≥3,和=11。 - r1=1, r2=1: k=1, minCols=3>n=3? 不,minCols=3==n=3,可以。
colSum=[0,6,5],pref=[0,0,6,11]。
minPref=0。
j=3: currentSum=11-0=11,更新 ans=11。
最终 ans=11。
复杂度分析
- 预处理前缀和:O(mn)。
- 枚举行对:O(m²)。
- 对每个行对,O(n) 时间求解一维问题。
- 总复杂度 O(m²n) 或 O(mn²)(通过转置保证 m≤n)。
- 空间复杂度 O(mn) 用于前缀和。
边界情况与注意事项
- S 可能为 0:此时面积至少 0 相当于无限制,就是经典的最大子矩阵和问题(可用 Kadane 思想在 O(m²n) 解决)。
- S 可能很大(如大于 m×n):此时无解(但题目通常保证有解,因为至少可以取整个矩阵)。
- 矩阵元素可能有负数,所以初始 ans 要设为 -inf。
- 一维问题中,minCols 可能为 0(当 S=0),此时需单独处理,或者用 Kadane 算法求最大子数组和(无长度限制)。
- 若 minCols>n,则该行数不可能满足面积≥S,直接跳过。
代码框架(Python)
import math
def maxSubmatrixSum(matrix, S):
m, n = len(matrix), len(matrix[0])
# 1. 二维前缀和
prefix = [[0]*(n+1) for _ in range(m+1)]
for i in range(1, m+1):
for j in range(1, n+1):
prefix[i][j] = matrix[i-1][j-1] + prefix[i-1][j] + prefix[i][j-1] - prefix[i-1][j-1]
ans = float('-inf')
# 2. 枚举上下行
for r1 in range(m):
for r2 in range(r1, m):
k = r2 - r1 + 1
minCols = (S + k - 1) // k # ceil(S/k)
if minCols > n:
continue
# 构建一维数组 colSum
colSum = [0]*n
for c in range(n):
# sum of rows r1..r2 in column c
colSum[c] = prefix[r2+1][c+1] - prefix[r1][c+1] - prefix[r2+1][c] + prefix[r1][c]
# 一维问题:长度至少 minCols 的最大和子数组
# 方法:前缀和+维护最小前缀和
pref = [0]*(n+1)
for c in range(1, n+1):
pref[c] = pref[c-1] + colSum[c-1]
minPref = pref[0]
for j in range(minCols, n+1):
current = pref[j] - minPref
if current > ans:
ans = current
# 更新 minPref 为 pref[j-minCols+1] 之前的最小值
# 实际上我们维护的是 pref[0..j-minCols] 的最小值
minPref = min(minPref, pref[j-minCols+1])
return ans
总结
这道题结合了二维前缀和、枚举优化和带长度限制的一维最大子数组和三个知识点。通过固定行边界将二维问题降为一维,再巧妙利用前缀和与最小前缀和来满足长度约束,最终在合理时间复杂度内求解。掌握这个套路,可以解决很多类似“带限制的子矩阵”问题。