固定子矩阵的上边界 top 和下边界 bottom 后,把这几行按列求和:
此时选择连续列 [left, right] 的总和,恰好等于二维子矩阵:
的元素和。二维问题因此转化为一维最大子数组问题,可以用 Kadane 算法在线性时间求解,并同时记录左右端点。
枚举 O(rows²) 组上下边界,每组执行 O(columns) 的列和更新和 Kadane,总时间复杂度为:
给定一个由正整数、负整数和 0 组成的矩阵,找出元素和最大的非空子矩阵。
返回:
其中:
(r1, c1) 是左上角坐标。(r2, c2) 是右下角坐标。0 开始的闭区间端点。示例:
坐标 [0, 1, 0, 1] 表示只选择右上角的 0。返回 [1, 0, 1, 0] 也同样正确。
题目约束:
所以矩阵一定非空。
假设固定:
将第 1~3 行按列相加后得到:
那么:
columnSums[0] 是原矩阵第 1~3 行、第 0 列的和。columnSums[left] + ... + columnSums[right] 是原矩阵第 1~3 行、第 left~right 列的矩形和。因此,固定上下边界后,只需在 columnSums 中寻找最大连续子数组。
为了避免每换一个 bottom 都重新求和,固定 top 后令 columnSums 初始全为 0,再逐行累加:
每扩展一次下边界只需 O(columns)。
dp 数组含义对固定的 [top, bottom],定义一维 DP:
为了恢复坐标,还需要知道这个连续子数组从哪一列开始。
代码不保存完整 dp 数组,而是使用:
currentSum:扫描到当前 right 时,以 right 结尾的最大连续子数组和,即压缩后的 dp[right]。currentLeft:currentSum 对应连续子数组的左端点。bestSum:到目前为止所有上下边界和左右区间中的最大和。answer:bestSum 对应的 [top, left, bottom, right]。columnSums 是二维到一维的压缩状态,currentSum 则是 Kadane DP 的空间压缩。
以 right 结尾的最大连续子数组只有两种选择:
columnSums[right]。dp[right - 1] 后面。因此:
如果此前的 currentSum <= 0,把它接到当前元素前面不会让结果更大,可以从当前列重新开始:
否则继续扩展:
这里使用 <= 0 而不是 < 0 只会在前缀和恰好为 0 时选择更靠后的等价起点,不影响最大和;题目允许多个答案时返回任意一个。
每次得到新的 currentSum 后,如果它严格大于 bestSum,就记录:
dp 数组如何初始化每次更换上边界 top 时:
因为还没有加入任何行。
每组 [top, bottom] 开始时:
扫描第一列时 currentSum <= 0,所以会正确地从第一列初始化实际状态。
必须使用:
不能初始化为 0。题目要求选择非空子矩阵,如果矩阵全为负数,答案应该是数值最大的那个负数,而不是不存在的空矩阵和 0。
例如:
正确答案对应单个元素 -2,坐标为 [0, 1, 0, 1]。
循环顺序为:
这样有两层复用:
top 后,新的 bottom 复用此前的列和。dp[right] 只复用 dp[right - 1]。不能在更换 top 后继续使用旧的 columnSums,否则会混入不属于当前上下边界的行。
dp 数组使用更完整的矩阵:
固定 top = 0,逐步扩展 bottom:
bottom | columnSums | 当前最大连续子数组 | 和 |
|---|---|---|---|
0 | [9, -8, 1, 3, -2] | [9] | 9 |
1 | [6, -1, 7, 1, 2] | 整个数组 | 15 |
2 | [12, -5, 3, 9, -5] | 下标 [0, 3] | 19 |
当 top = 0、bottom = 2 时,Kadane 的压缩 DP:
right | 当前列和 | dp[right] | 左端点 |
|---|---|---|---|
0 | 12 | 12 | 0 |
1 | -5 | 7 | 0 |
2 | 3 | 10 | 0 |
3 | 9 | 19 | 0 |
4 | -5 | 14 | 0 |
最大和为 19,对应坐标:
本实现根据题目约束独立整理,使用行区间压缩与 Kadane 算法恢复完整坐标。
bestSum 必须初始化为 -Infinity,保证选择一个真实元素。0 都是合法答案,严格 > 会保留最先遇到的答案。[上, 左, 下, 右],不是 [左, 上, 右, 下]。bottom 和 right 都属于子矩阵。top 要清零: columnSums 不能跨上边界复用。bottom 要累加: 不能直接覆盖上一轮列和。currentLeft 会返回错误坐标。设矩阵有 rows 行、columns 列:
O(rows²) 组。O(columns)。O(rows² × columns)。O(columns),用于 columnSums;Kadane 状态本身为 O(1)。当行数远大于列数时,可以交换压缩方向:枚举左右边界,把每一行压成一维数组,使复杂度变为 O(columns² × rows)。根据较小维度选择平方项,可得到:
但实现时必须同步转换返回坐标。题目两个维度都不超过 200,当前按行压缩的版本已经足够。