最大子矩阵问题

最大子矩阵问题的描述:
给你一个n*m的二维数组a(该数组a又可以称为而为矩阵或简称为矩阵),则该矩阵从第0行到第n-1行共n行元素,从第0列到第m-1列共m列元素,a[i][j]即为该矩阵的第i行第j列元素。我们称以第x1行第y1列的元素为左上角元素,以x2行y2列的元素为右下角元素(其中0<=x1<=x2<=n-1,0<=y1<=y2<=m-1),所得到的所有元素的集合为矩阵a的一个子矩阵。矩阵a的最大子矩阵即为a的子矩阵中元素和最大的那一个。
对于最大子矩阵问题,我们能够想到的最朴素的做法就是使用枚举算法进行遍历,代码如下:

int getMaxOfMatrix2d(int a[][], int n, int m) 
{
    int res = 0, tmp;
    for (int x1 = 0; x1 < n; x1 ++)
        for (int y1 = 0; y1 < m; y1 ++)
            for (int x2 = x1; x2 < n; x2 ++)
                for (int y2 = y1; y2 < m; y2 ++)
                {
                    tmp = 0;
                    for (int i = x1; i <= x2; i ++)
                        for (int j = y1; j <= y2; j ++)
                            tmp += a[i][j];
                    if (tmp > res)
                        res = tmp;
                }
    return res;
}

然而这种方法的时间复杂度达到了O(n3*m3),作为改进,我们可以用动态规划的方法首先求一个数组sum,sum[i][j]=a[0][j]+a[1][j]+...+a[i][j],即sum的状态转移方程为sum[i][j] = sum[i-1][j] + a[i][j] (如果i==0,则sum[i][j] = a[i][j]),sum[i][j]表示矩阵a第j列的前i+1行的元素之和(即a[0][j]到a[i][j]这i+1个元素之和),sum的求解代码如下:

for (int i = 0; i < n; i ++)
    for (int j = 0; j < m; j ++)
        if (i == 0)
            sum[i][j] = a[i][j];
        else
            sum[i][j] = sum[i-1][j] + a[i][j];

求解了sum之后,我们可以只需要开一个循环就能够求解某个固定(确定了起止行x1,x2和起止列y1,y2)的子矩阵的元素和,相比之前两个循环降低了一味地时间复杂度,代码如下:

for (int x1 = 0; x1 < n; x1 ++)
    for (int x2 = x1; x2 < n; x2 ++)
        for (int y1 = 0; y1 < m; y1 ++)
            for (int y2 = y1; y2 < m; y2 ++)
            {
                tmp = 0;
                for (int i = y1; i <= y2; i ++)
                    if (x1 == 0)
                        tmp += sum[x2][i];
                    else
                        tmp += sum[x2][i] - sum[x1-1][i];
                if (tmp > res)
                    res = tmp;
            }

不考虑之前的两层循环,我们可以发现里面的三层循环其实就是求一个“最大连续子序列和”(最大连续子序列和的问题我们之前讨论过,如果不记得了请记得回顾)的问题。对于一个长度为m的数组a,求其最大连续子序列和的代码如下:

int f = 0, res = a[0];
for (int i = 0; i < m; i ++)
{
    if (f < 0)
        f = a[i];
    else 
        f += a[i];
    if (f > res)
        res = f;
}

如果讲代码中的a[i]用sum[x2][i] - sum[x1-1][i]表示(如果x1==0时,a[i]则用sum[x2][i]表示),带入之前讲解的求最大子矩阵的表达式,则我们可以将之前求解最大子矩阵的程序的时间复杂度简化到O(n^2*m),代码如下:

int f, tmp, res = a[0][0];
for (int x1 = 0; x1 < n; x1 ++)
    for (int x2 = x1; x2 < n; x2 ++)
    {
        f = 0;
        for (int i = 0; i < m; i ++)
        {
            if (x1 == 0)
                tmp = sum[x2][i];
            else 
                tmp = sum[x2][i] - sum[x1-1][i];
            if (f < 0)
                f = tmp;
            else 
                f += tmp;
            if (f > res)
                res = f;
        }
    }

对比之前的代码,我们可以发现,原本在x1到x2行之前求解最大子矩阵的时间复杂度为O(m3),现在只需要O(m)的时间复杂的,再加上枚举其实行x1和结束行x2的两层循环,该求解最大子矩阵的算法的时间复杂度为O(n2*m)。

原文地址:https://www.cnblogs.com/xianyue/p/6895296.html