前言
本文是【程序员代码面试指南(第二版)学习笔记】C#版算法实现系列之一,用C#实现了《程序员代码面试指南》(第二版)栈和队列中的求最大子矩阵的大小。
一、题目要求
给定一个仅包含0和1的二维数组,要求找到最大的全部都是1的子矩阵,并返回该矩阵1的个数。
二、算法设计及代码实现
2.1 算法思想
概述:要就找到最大的子矩阵,最简单则是采取穷举法,但本文将采用遍历一次矩阵找出答案的方法进行解答。大体的过程是每一次遍历一行,遍历一行的时候记录这一行每一个元素所对应列的向上数的高度,也就是向上数有多少个连续的1。找到高度以后,就将该高度进行左右方向的延展,假如左右两边都比本身要高,那么就说明可以延展,则说明延展到了边界。这个时候找到的左右边界即宽,所需要的答案就是宽乘高。将每个元素都这样遍历计算以后找到最大的宽乘高就是答案。
记录高度是比较容易的,相对复杂的是找到左右边界。想要找到边界,其实就是要找到离自己最近的比自己低的高度,那么就可以采用单调栈进行计算。单调栈具体可以参考:最近的较小值序列(单调栈结构)
详细步骤:
1、初始化记录高度的数组(仅需要n长度的数组即可,因为遍历每一行的时候可以重复利用)。
2、遍历第i行。初始化该行要使用的单调栈。
3、遍历第i行的第j个元素。
1)首先要记录高度,如果第j个元素为1,则将第j个高度+1;否则归零。
2)如果当前元素所对应的高度不高于左边的高度,说明上一个高度已经找到了右边界,之后要找到左边界。左边界为单调栈中栈顶序列,如果单调栈为空,左边界应该是-1。随后计算面积,面积应当为栈顶高度 * (j - 栈顶序列 - 1)。
4、当一行遍历结束以后,单调栈中可能还有残留的元素。遍历剩余的每一个元素,由于这些元素到最后也没有找到右边界,所以右边界就是行长度n。而左边界就是栈顶下面一个序列,所以面积应当为栈顶高度 * (n - left - 1)
5、找到最大的矩阵后返回结果。
2.2 代码实现
public static int MaxRectSize(int[,] matrix) {
int m = matrix.GetLength(0);
int n = matrix.GetLength(1);
// 记录当前边为底向上的高度
int[] height = new int[n];
// 最大矩形面积
int ans = 0;
// 遍历每一行, 以当前行为底, 计算最大矩形面积
for (int i = 0; i < m; i++) {
// 单调栈, 用于判断每个位置的左右边界
Stack<int> stack = [];
// 遍历每一列, 计算当前列延展的最大矩形面积
for (int j = 0; j < n; j++) {
// 当前高度
height[j] = matrix[i, j] == 0 ? 0 : height[j] + 1;
// 找到右边界则出栈并计算面积
while (stack.Count > 0 && height[stack.Peek()] >= height[j]) {
int cur = stack.Pop();
int left = stack.Count > 0 ? stack.Peek() : -1;
ans = Math.Max(ans, height[cur] * (j - left - 1));
}
// 入栈
stack.Push(j);
}
// 计算剩余栈中的面积
while (stack.Count > 0) {
int cur = stack.Pop();
int left = stack.Count > 0 ? stack.Peek() : -1;
ans = Math.Max(ans, height[cur] * (n - left - 1));
}
}
return ans;
}