1.题目描述

面试题 17.24. 最大子矩阵

给定一个正整数和负整数组成的 N × M 矩阵,编写代码找出元素总和最大的子矩阵。

返回一个数组 [r1, c1, r2, c2],其中 r1, c1 分别代表子矩阵左上角的行号和列号,r2, c2 分别代表右下角的行号和列号。若有多个满足条件的子矩阵,返回任意一个均可。

注意:本题相对书上原题稍作改动

示例:

1
2
3
4
5
6
7
输入:
[
[-1,0],
[0,-1]
]
输出: [0,1,0,1]
解释: 输入中标粗的元素即为输出所表示的矩阵

说明:

  • 1 <= matrix.length, matrix[0].length <= 200

2.代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
class Solution {
public static int[] getMaxMatrix(int[][] matrix) {
//矩阵的行数
int row = matrix.length;
//矩阵的列数
int col = matrix[0].length;

//最值
int answerMax = Integer.MIN_VALUE;

//位置数组
int[] answerArray = new int[4];

//列上的前缀合
int[][] colPrefix = new int[row][col];
for (int i = 0; i < col; i++) {
//第一行各项的前缀合
colPrefix[0][i] = matrix[0][i];

//第二行之后的各项前缀合
for (int j = 1; j < row; j++) {
colPrefix[j][i] = colPrefix[j-1][i] + matrix[j][i];
}
}

//临时数组,用来保存接下来转化的一维数组
int[] temp = new int[col];

//依次枚举每一种转化的情况
for (int fromRow = 0; fromRow < row; fromRow++) {
for (int toRow = fromRow; toRow < row; toRow++) {
//fromRow到toRow进行合并
for (int i = 0; i < col; i++) {
temp[i] = fromRow == 0 ? colPrefix[toRow][i] : colPrefix[toRow][i] - colPrefix[fromRow-1][i];
}

//求解每一种情况的最大子数组和
int[] maxArrayResult = getMaxArray(temp);

//刷新最大的情况
int max = maxArrayResult[0];
int maxFromCol = maxArrayResult[1];
int maxToCol = maxArrayResult[2];
if (max > answerMax){
answerMax = max;

answerArray[0] = fromRow;
answerArray[1] = maxFromCol;
answerArray[2] = toRow;
answerArray[3] = maxToCol;
}
}
}

return answerArray;
}

/**
* 求解一维数组的最大子数组和
* @param nums
* @return
*/
private static int[] getMaxArray(int[] nums) {
//上一个以当前节点为终结节点的最大子数组和
int preMax = nums[0];

//当前的以当前节点为终结节点的最大子数组和
int max = nums[0];

//真正的起止列数
int realFromCol = 0;
int realToCol = 0;

//临时的开始列数
int fromCol = 0;

//枚举
for (int i = 1; i < nums.length; i++) {
if (preMax <= 0){
preMax = nums[i];

//如果需要刷新最大值和起止列
if (preMax > max){
max = preMax;

realFromCol = i;
realToCol = i;
}

//起始列发生了变化
fromCol = i;
}else {
preMax = preMax + nums[i];

//如果需要刷新最大值
if (preMax > max){
max = preMax;

realFromCol = fromCol;
realToCol = i;
}
}
}

return new int[]{max, realFromCol, realToCol};
}
}

1. 解题思路:

既然做到了这一题,那么我们肯定已经遇到了最大子序和的问题。此题是最大子序和问题的升级版本,从一维升级到了二维。我们主要的解题思路就是将二维问题降为一维问题方便解决。

2. 操作解读:

1> 如何从二维问题降低为一维问题?

  • 求出二维数组(矩阵)的列上的前缀合,便于枚举。(代码的 15 ~ 25 行)
  • 枚举各种组合,就是某两列的前缀合相减。(代码 30 ~ 36 行)

2> 降低为一维之后,我们求得都是最大值,如何返回范围?

  • 首先范围所在的行在枚举情况的时候就已经确定了,范围所在的列通过求解一维数组的函数返回。
  • 因为某些原因,导致最大和从一个新的数字开始,那么就需要一个临时的变量来记录起始值的变化,这就是代码(76 行)的作用。