ACM入门题-最大子矩阵暴力枚举-Go语言
问题描述
我们这里所描述的矩阵即为一个n*m
的二维数组,它的大小由数组内所有元素之和来决定。子矩阵则是一段上下左右连续的子数组,如图所示都为子矩阵。
解决思路
最终的结果是一个标量,而我们在一个二维数组里寻找这个答案。所以要有降维打击的数学思想。
如果给我们的不是一个矩阵,而只是一个向量(一维数组),那我们就可把问题化解为最大连续子序列和问题。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j14sSo3x-1649008075884)(C:\Users\jackonessalad\AppData\Roaming\Typora\typora-user-images\image-20220404002652528.png)]
对于一维的数组而言我们可以动态规划
- 状态:
dp[i]
代表以nums[i]
结尾的最大连续子序列和 - 状态转移方程:
dp[i] = max(nums[i]+dp[i-1],nums[i])
- 结果:
res = max(res,dp[i])
- 实际上这个dp数组我们只用一次,且前一次添加的值,所以我们直接用一个整数变量来记录中间值即可。
tmp = max(nums[i]+tmp,nums[i]),res = max(res,tmp)
那我们如何把这个解法扩展到二维数组呢?
我要先列出二维数组压缩成一维后的可能状态(以上下压缩来做):
dp[i] = nums[1]+...+nums[i]
代表前 i 行之和- 那么任意几行之和就是
nums[j]+...+nums[i](i>j) = dp[i] - dp[j-1] = dpLine
- 对于某几行之和中间数组
dpLine
再求最大连续子数组之和就是原矩阵的子矩阵之和
也就是说,我们接下来要对以上1+2+3+4=10个 dpLine
数组做最大连续子序列和的处理即可得到我们要的最大子矩阵。
代码实现
package main
import "fmt"
func main() {
var n, m int
fmt.Scan(&n, &m)
a := make([][]int, n+1) // 二维数组行数都设为n+1,预留一行
for i, _ := range a {
a[i] = make([]int, m+1)
}
for i := 1; i <= n; i++ {
for j := 1; j <= m; j++ {
fmt.Scan(&a[i][j])
}
}
dp := make([][]int, n+1) // dp[i] 表示a前i行和数组
for i, _ := range dp {
dp[i] = make([]int, m+1)
}
for i := 1; i <= n; i++ {
for j := 1; j <= m; j++ {
for k := 1; k <= i; k++ {
dp[i][j] += a[k][j] // dp[i][j] = a[1][j]+a[2][j]+...+a[i][j]
}
}
}
res := -0x3f3f3f // 结果
tmp := 0 // 保存可能的最大值
dpLine := make([]int, m+1) // 存储a[i]~a[j]和的中间变量
var max = func(a, b int) int {
if a > b {
return a
}
return b
}
for i := 1; i <= n; i++ {
for j := 0; j < i; j++ {
for k := 1; k <= m; k++ {
dpLine[k] = dp[i][k] - dp[j][k] // a[i]~a[j]和
}
tmp = 0
for k := 1; k <= m; k++ {
// 转化为一维问题
tmp = max(tmp+dpLine[k], dpLine[k])
res = max(res, tmp)
}
}
}
fmt.Println(res)
}