携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第32天,点击查看活动详情
题目描述
给定一个正整数、负整数和 0 组成的 N × M 矩阵,编写代码找出元素总和最大的子矩阵。
返回一个数组 [r1, c1, r2, c2],其中 r1, c1 分别代表子矩阵左上角的行号和列号,r2, c2 分别代表右下角的行号和列号。若有多个满足条件的子矩阵,返回任意一个均可。
注意:本题相对书上原题稍作改动
示例:
输入: [ [-1,0], [0,-1] ] 输出:[0,1,0,1] 解释:输入中标粗的元素即为输出所表示的矩阵
说明:
- 1 <= matrix.length, matrix[0].length <= 200
解题思路
给定一矩阵matrix,求矩阵中元素和最大的矩阵的左上角和右下角坐标;
此题可以转化成求给定矩阵中的子矩阵元素和的最大值。即需要遍历所有可能组合成的矩阵情况,然后取最大值。
矩阵遍历
- 假定矩阵matrix的行为n,列为m,当前子矩阵最大值为max,存放坐标的数组res;
- 设定子矩阵的上边界,上边界范围为[0,n](第一层循环);
- 设定子矩阵的下边界,下边界的范围为[上边界,n](第二层循环);
- 设定完子矩阵的上下边界后,需要遍历子矩阵的左右边界。左右边界的确立有一个小技巧,可能很多同学这里会有点迷糊,里面为什么只有一个遍历列的循环?
按照上下边界的方式,这里应该有2个循环来遍历左右边界。
重点来了,上下边界确定了,那子矩阵中的每一列的值都可以依靠循环下边界的列获得。即我们用一个数组来盛放每一列,从子矩阵的上边界到子矩阵的下边界的当前列的和。
则在向右移动列的过程中,实际上是可以对子矩阵求和的,所以这里可以依靠一个sum值,即当前子矩阵的所有列的和来判断子矩阵左右边界是否需要移动。
- 设定一个值为sum,代表选中的子矩阵的所有列的和。一个leftX代表子矩阵的左上角行的坐标,leftY代表子矩阵左上角列的坐标;
- 每一次移动列,对sum值进行判断,如果当前sum大于0,则说明子矩阵的范围还可以向后继续延申,因为如果子矩阵右边界新加的一列的值如果为正数的话,子矩阵的和就增大了,所以它有增大的可能性,sum值累加上 当前列的值;
- 如果当前sum小于等于0,则这个子矩阵右侧的矩阵的值如果比0大的话则一定比当前子矩阵大,所以需要将子矩阵的左上坐标重新设置成当前的上边界和当前遍历的列值,将sum设置成当前列的和,以方便下一次对子矩阵求和;
- 设置完左上角的坐标后,再判断当前sum与max的大小,如果比max大,则将max设置成sum,将之前存放的左上角坐标放入res中,且将当前的下边界的值和当前列的值放入右下角坐标中。
- 遍历完成后,res中存放的就是最大矩阵的值。
- 从第0行0列开始遍历,每一次将区域内的所有值进行累加,比较并设置最大者。根据比较结果实时更新坐标数据。
代码实现
public static int[] getMaxMatrix(int[][] matrix) {
// 保存结果的坐标数据
int[] res = new int[4];
// 矩阵的行
int n = matrix.length;
// 矩阵的列
int m = matrix[0].length;
int max = Integer.MIN_VALUE;
int leftX = 0;
int leftY = 0;
// 遍历矩阵行列
for (int i = 0; i < n; i++) {
// 创建一个数组记录每一列的累加和
int[] collSum = new int[m];
// 子矩阵的下边界
for (int downLine = i; downLine < n; downLine ++) {
int sum = 0;
// 寻找上下边界内和最大的子矩阵
for (int j = 0;j < m ; j++) {
collSum[j] += matrix[downLine][j];
if (sum > 0) {
sum += collSum[j];
} else {
sum = collSum[j];
leftX = i;
leftY = j;
}
if (sum > max) {
max = sum;
res[0] = leftX;
res[1] = leftY;
res[2] = downLine;
res[3] = j;
}
}
}
}
return res;
}