O(N^3)找最大子矩阵Submatrix

博客探讨了如何从寻找最大子序列的问题出发,通过动态规划优化算法,降低时间复杂度,最终达到O(N)。文章详细介绍了动态规划的状态转移方程,并提供了实现最大子数组和的代码示例。接着,将这种方法应用到最大子矩阵问题,虽然复杂度仍然是O(N^3),但比直接方法有所改进。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

其实这个方法是从找最大子序列演化过来的

找最大子序列的话,最直接的方法是确定左右边界,然后把这之间的加起来,与当前最大值比较,这样做法复杂度为O(N^3)

稍微优化一下的话,是确定一边,然后一个一个加上去,每次与当前最大值比较,这样做法复杂度为O(N^2)

再优化一下,就是从第一个开始加,每加一次之后与当前最大值比较,一旦和小于0,就把和清零,因为说明前面的那段对最大没有贡献,复杂度为O(N)

这其实也是动态规划的思想

令b[j]表示从a[0]~a[j]的最大子段和,b[j]的当前值只有两种情况,(1) 最大子段一直连续到a[j]  (2) 以a[j]为起点的子段,不知有没有读者注意到还有一种情况,那就是最大字段没有包含a[j],如果没有包含a[j]的话,那么在算b[j]之前的时候我们已经算出来了,注意我们只是算到位置为j的地方,所以最大子断在a[j]后面的情况我们可以暂时不考虑。
由此我们得出b[j]的状态转移方程为:b[j]=max{b[j-1]+a[j],a[j]},
所求的最大子断和为max{b[j],0<=j<n}。进一步我们可以将b[]数组用一个变量代替。
得出的算法如下:
    int maxSubArray(int n,int a[])
    {
        int b=0,sum=-10000000;
        for(int i=0;i<n;i++)
        {
             if(b>0) b+=a[i];
             else b=a[i];
             if(b>sum) sum=b;  
        }
        return sum;
    }
这就是第三种方法-动态规划。

 

然后现在再来看矩阵,如果把最大子矩阵分成这么几种情况:从第i行到第j行,这里就需要O(N^2),然后对于选定的行边界,再把这两个边界之间所有行的同一列相加,就可以合并得到一个序列,接下来就是求最大子序列问题,因此再乘上O(N),总的复杂度即为O(N^3)

 

顺便提一句,之前的对于子序列的O(N^3)与O(N^2)分别对应的方法在求最大子矩阵里为O(N^6)与O(N^4)

### 关于最大子矩阵问题的解决方案 最大子矩阵问题是经典的动态规划问题之一,其目标是从一个给定的矩阵中出具有最大和的子矩阵。以下是解决该问题的一种常见方法。 #### 方法概述 一种常见的策略是通过降维的方式将二维问题转化为一维问题处理。具体来说,可以通过计算每一列的部分和(即前缀和),从而将二维数组压缩成多个一维数组。对于这些一维数组,可以应用类似于求解最大子段和的方法来得到最优解[^1]。 #### 前缀和的应用 为了高效地计算子矩阵的和,通常采用二维前缀和的技术。设 `prefixSum[x2][y2]` 表示从 `(1, 1)` 到 `(x2, y2)` 的矩形区域内所有元素的总和,则任意子矩阵 `(x1, y1), (x2, y2)` 的和可通过如下公式快速得出: \[ \text{subMatrixSum} = \text{prefixSum}[x2][y2] - \text{prefixSum}[x1-1][y2] - \text{prefixSum}[x2][y1-1] + \text{prefixSum}[x1-1][y1-1] \] 此公式的推导基于二维前缀和递推关系[^3]。 #### 动态规划实现 下面是一个完整的 C++ 实现代码,展示了如何结合前缀和与动态规划技术解决问题: ```cpp #include <bits/stdc++.h> using namespace std; const int MAXN = 105; int matrix[MAXN][MAXN]; int prefixSum[MAXN][MAXN]; // 计算二维前缀和 void computePrefixSum(int n, int m) { for (int i = 1; i <= n; ++i) { for (int j = 1; j <= m; ++j) { prefixSum[i][j] = matrix[i][j] + prefixSum[i-1][j] + prefixSum[i][j-1] - prefixSum[i-1][j-1]; } } } // 获取子矩阵和 int getSubmatrixSum(int x1, int y1, int x2, int y2) { return prefixSum[x2][y2] - prefixSum[x1-1][y2] - prefixSum[x2][y1-1] + prefixSum[x1-1][y1-1]; } int main() { int n, m; cin >> n >> m; // 输入矩阵数据 for (int i = 1; i <= n; ++i) { for (int j = 1; j <= m; ++j) { cin >> matrix[i][j]; } } computePrefixSum(n, m); int maxi = INT_MIN; // 枚举左边界和右边界 for (int l = 1; l <= m; ++l) { for (int r = l; r <= m; ++r) { vector<int> dp(n + 1, 0); for (int i = 1; i <= n; ++i) { dp[i] = max(dp[i-1] + getSubmatrixSum(i, l, i, r), getSubmatrixSum(i, l, i, r)); maxi = max(maxi, dp[i]); } } } cout << maxi << endl; return 0; } ``` 上述代码实现了以下功能: 1. **输入读取**:接收矩阵尺寸及其元素。 2. **前缀和预处理**:加速后续子矩阵和查询操作。 3. **动态规划核心逻辑**:枚举每一对列作为左右边界,并在此基础上执行一次最大子段和运算。 这种方法的时间复杂度为 \(O(N^2M)\),其中 \(N\) 是行数,\(M\) 是列数,在合理范围内能够满足性能需求[^5]。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值