LeetCode 1727.重新排列后的最大子矩阵
1. 题目描述
给你一个大小为 m x n 的二进制矩阵 matrix ,你可以对矩阵的每一行的列进行任意重新排列(即每一行内部可以任意交换列),但不能改变行之间的相对顺序。请你返回在重新排列后,全为 1 的最大子矩阵的面积。
示例:
输入:matrix = [[1,0,1],[1,1,1],[1,1,1]]
输出:6
解释:将第0行的列1和列2交换后,可得到3行2列的全1矩形(高度3,宽度2),面积为6。
2. 解题思路
本题与 LeetCode 85 题「最大矩形」类似,但增加了一个关键条件:每一行的列可以任意重排。这意味着我们不再受限于列原有的顺序,可以自由地将高度较高的列聚在一起,从而构造更大的矩形。
核心思想:
- 高度累积 :与85题一样,定义
h[j]表示从当前行向上(包括当前行)连续1的个数。如果当前行某列为0,则高度清零(连续中断)。 - 排序求最优 :由于可以重排列,我们不需要关心列的顺序,只需要知道哪些列的高度足够高。对当前行的高度数组
h进行升序排序 ,得到hs。
假设我们想构造一个高度为h的矩形,那么所有高度 ≥ h 的列都可以参与。在排序后的数组中,对于每个位置i,hs[i]是当前值,且它右边的所有列(包括自己)的高度都 ≥hs[i],因此以hs[i]为高的矩形最大可能宽度为n - i(n为列数)。
遍历所有i,计算(n - i) * hs[i]并更新当前行的最大值。 - 逐行计算:对每一行都做上述操作,并取所有行的最大值作为最终答案,因为任何全1矩形必然有底边在某一行。
3. 代码实现
cpp
class Solution {
public:
int largestSubmatrix(vector<vector<int>>& matrix) {
int n = matrix[0].size(); // 列数
vector<int> h(n, 0); // 记录每一列的累积高度
int res = 0;
for (auto& row : matrix) { // 逐行遍历
// 更新高度数组
for (int j = 0; j < n; j++) {
if (row[j] == 1)
h[j]++; // 连续1,高度+1
else
h[j] = 0; // 遇到0,高度清零
}
// 对当前行的高度数组进行排序
auto hs = h; // 复制一份(避免破坏原顺序)
sort(hs.begin(), hs.end()); // 升序排序
// 遍历排序后的高度,计算可能的最大矩形面积
for (int i = 0; i < n; i++) {
// 以 hs[i] 作为矩形的高,宽度为从 i 到末尾的列数
res = max(res, (n - i) * hs[i]);
}
}
return res;
}
};
3.1 代码详解
- 第4行 :
int n = matrix[0].size();获取列数。 - 第5行 :
vector<int> h(n, 0);初始化高度数组,每列高度初始为0。 - 第7行:外层循环遍历每一行。
- 第9-13行:内层循环更新高度。如果当前元素是1,则高度加1;否则高度归零(因为连续1中断)。
- 第16行 :复制
h到hs,因为排序会改变数组顺序,而下一行需要用到原始的h(累积高度)。 - 第17行 :对
hs进行升序排序。排序后,hs[0]最小,hs[n-1]最大。 - 第19-21行 :遍历排序后的数组,以每个
hs[i]作为矩形的高。由于hs[i]是升序的,所以对于下标i,所有j >= i的hs[j]都 ≥hs[i],即至少有n - i列的高度不低于hs[i]。因此,以hs[i]为高的矩形最大宽度就是n - i,面积为(n - i) * hs[i]。取所有i中的最大值作为当前行的最佳矩形面积,并更新全局结果res。 - 最终返回
res。
3.2 示例运行
以题目示例 matrix = [[1,0,1],[1,1,1],[1,1,1]] 为例:
- 第0行 :
row = [1,0,1]
更新高度:h = [1,0,1]
排序后hs = [0,1,1]
计算:- i=0: (3-0)*0 = 0
- i=1: (3-1)*1 = 2
- i=2: (3-2)*1 = 1 → 最大为2
- 第1行 :
row = [1,1,1]
更新高度:上一行[1,0,1],当前行加1 →[2,1,2]
排序后hs = [1,2,2]
计算:- i=0: 3*1 = 3
- i=1: 2*2 = 4
- i=2: 1*2 = 2 → 最大为4
- 第2行 :
row = [1,1,1]
更新高度:[3,2,3]
排序后hs = [2,3,3]
计算:- i=0: 3*2 = 6
- i=1: 2*3 = 6
- i=2: 1*3 = 3 → 最大为6
最终结果 res = max(2,4,6) = 6。
4. 复杂度分析
- 时间复杂度 :O(m * n log n)
- 每行更新高度 O(n)
- 每行排序 O(n log n)
- 总共有 m 行,因此总复杂度 O(m * n log n)
(如果使用计数排序可以优化到 O(m * n),但本题 n ≤ 10^5 且元素为 0/1,排序的 log n 可接受)
- 空间复杂度 :O(n)
只需要一维数组h和临时数组hs(排序需要额外 O(n) 空间,但可以就地排序,不过为了保持原数组我们复制了一份,所以额外 O(n))。
5. 与 LeetCode 84/85 的对比
| 题目 | 特点 | 核心算法 | 关键差异 |
|---|---|---|---|
| 84 | 一维柱状图,不可重排 | 单调栈 | 找左右边界 |
| 85 | 二维01矩阵,不可重排 | 逐行累积高度 + 84解法 | 转化为一维,单调栈 |
| 1727 | 二维01矩阵,可重排列 | 逐行累积高度 + 排序 | 利用重排特性,排序后取最大宽度 |
相同点 :都使用了高度累积 的技巧,将二维问题转化为一维问题。
不同点:85题不能重排,因此需要用单调栈找到实际的左右边界;1727题可以重排,所以排序后直接取最宽的列数即可。
6. 总结
本题的关键在于理解"重排列"带来的便利:我们只需关注每一列的高度,而无需关心它们的原始顺序。通过累积高度并对每行排序,可以轻松求出以当前行为底边的最大可能矩形面积。这种思想在涉及列重排的矩阵问题中非常有用,值得掌握。