前言
最近在学dp(dynamic programming:动态规划)的时候遇到了一道最大子矩阵的问题,当时即使学了最大序列和,但看到这个问题时还是无从下手,不知道如何确定最大子矩阵,然后去看别人的代码,还是有点看不懂别人在写啥,所以这篇博客记录自己对这个问题理解~
分析
这里就不介绍最大序列和了,如果没了解过的话需要补充一下这方面的知识,点此跳转,但他在讲最大子矩阵的时候,并没有过多阐述如何得到最优子矩阵,而是告诉我们如何计算最优子矩阵的值,然后给出代码,导致看的懵懵懂懂。所以也就为啥有了这篇博客的原因了~
事实上,计算最大子矩阵是把二维数组,压缩成一维数组,来计算该一维数组的最大序列和。当然这么说,很多人都是很迷糊的,具体往下看。
首先,我们如何计算一个矩阵大小(定义为矩阵中所有元素的和。)
最简单的方法就是先把每行的元素都相加,最后结果变成一列,然后把这一列元素相加,最终得到矩阵的大小(如图1左图所示)。或者是先把每一列的元素都相加,最后结果变成一行,然后这一行元素相加,最终得到矩阵的大小(如图1右图所示),这就是上面说的2维压缩成1维的思想。
为了方便理解使用最长序列和,我们采用图1右图的方式计算最大子矩阵。
图1
那么问题来了,如何找到最大的那个子矩阵呢?最简单的方法是,一个矩阵中能有多少个满列子矩阵,那么我们就枚举所有的满列子矩阵。找满列子矩阵的原因是因为我们计算最大子矩阵的时候,是采用2维压缩成1维的方式计算的,于是对于压缩后的每一行,采用最长序列和计算该行中哪几个连续的列组成的矩阵最大,并记录子矩阵中最大的那一个就好了。
文字看不懂得话看下面的图.ps:下图的abcd指的是a行,b行,c行,d行。
图2
于是我们得到了10个(1+2+3+4)满列子矩阵,图中展示的是满列子矩阵压缩成1维的样子。换句话来说,这样我们就得到了10个一维序列,于是我们只需要用求最长序列和的方式,计算这10个一维序列中,哪个子序列和最大,并记录就好了,其结果也就是我们所要求的最大子矩阵了。
我们站在已知图2中所求矩阵的结果的角度往回看,图中的最大子矩阵为,压缩成1维的话对应图2中的(d+c+b)所表示的满列子矩阵,由于该序列的最大序列和是15也就是前两个数相加,从而该值就是最大子矩阵的大小。
至此,我们已经在理论上知道如何求一个矩阵的最大子矩阵大小了,现在我们看看代码如何书写
代码
#include <iostream>
#include <limits>
#include <vector>
#include <numeric>
using namespace std;
// 查找一维序列的最长子序列和
// 如果不知道如何求最长子序列和,建议看看上文中的链接,这里不过多赘述
int get_max_sequence_sum(vector<int> arr){
for(int i=1;i<arr.size();++i){//dp思想
arr[i]=max(arr[i-1]+arr[i],arr[i]);
}
int ans=arr[0];//寻找最长子序列和的结果
for(auto x:arr){
if(x>ans)ans=x;
}
return ans;
}
int main() {
int n;
cin>>n;
vector<vector<int>> matrix(n,vector<int>(n,0));//初始化矩阵
int v;
for(int i=0;i<n;++i){
for (int j=0; j<n; ++j) {
cin>>v;
matrix[i][j]=v;//初始化矩阵的大小
}
cin.ignore();
}
int ans=numeric_limits<int>::min();//ans记录最大子矩阵的大小,初始为最小值
for(int i=0;i<n;++i){//这一层循环控制i行的加入,也就是图2三条虚线的四个过程
vector<int> col_sum(n,0);//满列子矩阵的一维表示
for(int j=i;j>=0;--j){//这一层循环控制当前所在的行共有多少个满列子矩阵
/**
*计算满列矩阵,具体计算过程如图2所示
* 如j=i=c行,那么计算的过程是先算c行,在算c+b,最后在算c+b+a
*/
for(int k=0;k<n;++k){
col_sum[k]+=matrix[j][k];
}
// 每得到一个一维序列,也就是图2的10个,就计算该序列的最大子序列和
int pos=get_max_sequence_sum(col_sum);
if(pos>ans)ans=pos;//记录最大值
}
}
cout<<ans<<endl;
}
这就是最大子矩阵的计算方式啦,大家看到这可以去尝试手动ac上面那道最大子矩阵的题啦~