背景及必备知识
总所周知,矩阵实质就是一个二维数组。所以想要求最大子矩阵,不如先来看看如何求一位数组的最大子序列和吧!
- 现假设有一个一维数组 arr[n],要你找出连续的一段数组元素,使其和最大
- 例如,一个一维数组:4 -5 6 3 7 -1 8 ,通过肉眼观察, 6 3 7 -1 8 为这个数组的连续最大和。那么有什么算法能够解决这个问题呢?
我们可能看一眼就能想到的方法就是用三层循环来枚举吧!
是不是像这样~~
int max_sum, tmp_sum;
for(int i = 0, i < n; i++) {
for(int j = 0; j <= i; j++) {
tmp_sum = 0;
for(int k = j; k <= i; k++) {
tmp_sum += arr[k];
}
if (tmp_sum > max_sum)
max_sum = tmp_sum;
}
}
这样确实可以,但显然时间复杂度太高了,不好。
于是我向大家隆重推出今天的主角:用动态规划,以O(n)的复杂度来做。(我还是结合代码来讲吧)
int get_maxsum(int arr[],int m)
{
int t=0;
int Max=0;
for(int i=1;i<=m;i++)
{
if(t>=0) t+=arr[i];//如果前面累加的大于0,说明它能增大当前数
//那当前最优策略就是让当前的数加入原先的序列
else t=arr[i];//如果前面累加的数小于等于0,则它对当前数无帮助
//则还不如以当前的数另起一个序列
Max=max(Max,t);//记录
}
return Max;
}
问题分析
好了,现在我们知道了如何求一维数组中的最大子序列。
那如何把一维拓展到二维呢?(先自己想想吧!)
其实很简单~~
先来看张图片
- 假设我们要求的最大子矩阵是原矩阵的第 i 行到第 j 行,第 k 列到第 s 列,那么如下图所示,圈出的范围即最大子矩阵。我们将最大子矩阵的每一列各自加和,就可以得到一个一维数组
- 该一维数组的各个元素为a[i][k] +······+ a[j][k], ···+···+··· , a[i][s] +······+ a[j][s]。
从上面的idea中我们可以得出解决此问题的基本思想:
- 控制新的子矩阵开始,每列中的元素累加变成一维数组中的各个元素,然后再求一维数组最大子序列的和。
懂了吗?
还有点昏的话,看看代码吧!
Code
#include<bits/stdc++.h>
using namespace std;
int n,m;
int Map[120][120],max_sum=0;
int work(int arr[],int m)//前面讲过了,就是dp的方法
{
int t=0;
int Max=0;
for(int i=1;i<=m;i++)
{
if(t>=0) t+=arr[i];
else t=arr[i];
Max=max(Max,t);
}
return Max;
}
int main()
{
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
scanf("%d",Map[i]+j);//用Map数组存储该矩阵
}
}
int col[120];
for(int i=1;i<=n;i++)//控制所累加列的起点
//这层i循环及下面的j循环放在一起起了枚举了所有行层的作用(这里有点儿难理解,好好想想)
{
memset(col,0,sizeof(col));//以新的起点开始累加
for(int j=i;j<=n;j++)//控制所累加列的终点
{
for(int k=1;k<=m;k++)//对各个列进行累加操作
{
col[k]+=Map[j][k];
}
max_sum=max(max_sum,work(col,m));//求解这些列所构成的一位数组的最大子序列
}
}
cout<<max_sum;
return 0;
}
这就好了呀!对,就这么巧妙!
好好回味一下吧,相信你最后一定能获得一种别有洞天的感觉!
学习厌倦了?点我有更多精彩哦!