【华为OD】最大子矩阵和
题目描述
给定一个二维整数矩阵,要在这个矩阵中选出一个子矩阵,使得这个子矩阵内所有的数字和尽量大。我们把这个子矩阵称为"和最大子矩阵",子矩阵的选取原则,是原矩阵中一段相互连续的矩形区域。
输入描述
- 输入的第一行包含两个整数 N,M (1 <= N, M <= 10)
- 表示一个 N 行 M 列的矩阵
- 下面有 N 行,每行有 M 个整数
- 同一行中每两个数字之间有一个空格
- 最后一个数字后面没有空格
- 所有的数字得在 -1000 ~ 1000 之间
输出描述
输出一行,一个数字。表示选出的"和最大子矩阵"内所有数字的和
示例
输入:
3 4
-3 5 -1 5
2 4 -2 4
-1 3 -1 3
输出:
20
说明:
一个3*4的矩阵中后面3列的和为20,和最大
解题思路
这是一个经典的最大子矩阵和 问题,是最大子数组和问题在二维空间的扩展。
核心思想:
- 枚举所有可能的上下边界
- 对于每一对上下边界,将问题转化为一维的最大子数组和问题
- 使用Kadane算法求解一维最大子数组和
我将提供两种解法:暴力枚举法 和优化的Kadane算法。
解法一:暴力枚举法
暴力枚举所有可能的子矩阵,计算每个子矩阵的和,找出最大值。
Java实现
java
import java.util.*;
public class Solution1 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int m = sc.nextInt();
int[][] matrix = new int[n][m];
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
matrix[i][j] = sc.nextInt();
}
}
int maxSum = Integer.MIN_VALUE;
// 枚举所有可能的子矩阵
for (int r1 = 0; r1 < n; r1++) {
for (int c1 = 0; c1 < m; c1++) {
for (int r2 = r1; r2 < n; r2++) {
for (int c2 = c1; c2 < m; c2++) {
// 计算子矩阵 (r1,c1) 到 (r2,c2) 的和
int sum = 0;
for (int i = r1; i <= r2; i++) {
for (int j = c1; j <= c2; j++) {
sum += matrix[i][j];
}
}
maxSum = Math.max(maxSum, sum);
}
}
}
}
System.out.println(maxSum);
}
}
Python实现
python
def solve_brute_force():
n, m = map(int, input().split())
matrix = []
for _ in range(n):
row = list(map(int, input().split()))
matrix.append(row)
max_sum = float('-inf')
# 枚举所有可能的子矩阵
for r1 in range(n):
for c1 in range(m):
for r2 in range(r1, n):
for c2 in range(c1, m):
# 计算子矩阵 (r1,c1) 到 (r2,c2) 的和
current_sum = 0
for i in range(r1, r2 + 1):
for j in range(c1, c2 + 1):
current_sum += matrix[i][j]
max_sum = max(max_sum, current_sum)
print(max_sum)
solve_brute_force()
C++实现
cpp
#include <iostream>
#include <vector>
#include <climits>
using namespace std;
int main() {
int n, m;
cin >> n >> m;
vector<vector<int>> matrix(n, vector<int>(m));
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
cin >> matrix[i][j];
}
}
int maxSum = INT_MIN;
// 枚举所有可能的子矩阵
for (int r1 = 0; r1 < n; r1++) {
for (int c1 = 0; c1 < m; c1++) {
for (int r2 = r1; r2 < n; r2++) {
for (int c2 = c1; c2 < m; c2++) {
// 计算子矩阵 (r1,c1) 到 (r2,c2) 的和
int sum = 0;
for (int i = r1; i <= r2; i++) {
for (int j = c1; j <= c2; j++) {
sum += matrix[i][j];
}
}
maxSum = max(maxSum, sum);
}
}
}
}
cout << maxSum << endl;
return 0;
}
解法二:Kadane算法优化
通过固定上下边界,将二维问题转化为一维最大子数组和问题,使用Kadane算法求解。
Java实现
java
import java.util.*;
public class Solution2 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int m = sc.nextInt();
int[][] matrix = new int[n][m];
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
matrix[i][j] = sc.nextInt();
}
}
int maxSum = Integer.MIN_VALUE;
// 枚举所有可能的上下边界
for (int top = 0; top < n; top++) {
int[] temp = new int[m];
for (int bottom = top; bottom < n; bottom++) {
// 将第bottom行加到temp数组中
for (int j = 0; j < m; j++) {
temp[j] += matrix[bottom][j];
}
// 对temp数组使用Kadane算法找最大子数组和
int currentMax = kadane(temp);
maxSum = Math.max(maxSum, currentMax);
}
}
System.out.println(maxSum);
}
// Kadane算法求最大子数组和
private static int kadane(int[] arr) {
int maxSoFar = arr[0];
int maxEndingHere = arr[0];
for (int i = 1; i < arr.length; i++) {
maxEndingHere = Math.max(arr[i], maxEndingHere + arr[i]);
maxSoFar = Math.max(maxSoFar, maxEndingHere);
}
return maxSoFar;
}
}
Python实现
python
def kadane(arr):
"""Kadane算法求最大子数组和"""
max_so_far = arr[0]
max_ending_here = arr[0]
for i in range(1, len(arr)):
max_ending_here = max(arr[i], max_ending_here + arr[i])
max_so_far = max(max_so_far, max_ending_here)
return max_so_far
def solve_kadane():
n, m = map(int, input().split())
matrix = []
for _ in range(n):
row = list(map(int, input().split()))
matrix.append(row)
max_sum = float('-inf')
# 枚举所有可能的上下边界
for top in range(n):
temp = [0] * m
for bottom in range(top, n):
# 将第bottom行加到temp数组中
for j in range(m):
temp[j] += matrix[bottom][j]
# 对temp数组使用Kadane算法找最大子数组和
current_max = kadane(temp)
max_sum = max(max_sum, current_max)
print(max_sum)
solve_kadane()
C++实现
cpp
#include <iostream>
#include <vector>
#include <climits>
#include <algorithm>
using namespace std;
// Kadane算法求最大子数组和
int kadane(vector<int>& arr) {
int maxSoFar = arr[0];
int maxEndingHere = arr[0];
for (int i = 1; i < arr.size(); i++) {
maxEndingHere = max(arr[i], maxEndingHere + arr[i]);
maxSoFar = max(maxSoFar, maxEndingHere);
}
return maxSoFar;
}
int main() {
int n, m;
cin >> n >> m;
vector<vector<int>> matrix(n, vector<int>(m));
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
cin >> matrix[i][j];
}
}
int maxSum = INT_MIN;
// 枚举所有可能的上下边界
for (int top = 0; top < n; top++) {
vector<int> temp(m, 0);
for (int bottom = top; bottom < n; bottom++) {
// 将第bottom行加到temp数组中
for (int j = 0; j < m; j++) {
temp[j] += matrix[bottom][j];
}
// 对temp数组使用Kadane算法找最大子数组和
int currentMax = kadane(temp);
maxSum = max(maxSum, currentMax);
}
}
cout << maxSum << endl;
return 0;
}
解法三:前缀和优化(额外解法)
使用二维前缀和来快速计算任意子矩阵的和。
Java实现
java
import java.util.*;
public class Solution3 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int m = sc.nextInt();
int[][] matrix = new int[n][m];
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
matrix[i][j] = sc.nextInt();
}
}
// 构建前缀和数组
int[][] prefixSum = new int[n + 1][m + 1];
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
prefixSum[i][j] = matrix[i-1][j-1] + prefixSum[i-1][j]
+ prefixSum[i][j-1] - prefixSum[i-1][j-1];
}
}
int maxSum = Integer.MIN_VALUE;
// 枚举所有可能的子矩阵
for (int r1 = 0; r1 < n; r1++) {
for (int c1 = 0; c1 < m; c1++) {
for (int r2 = r1; r2 < n; r2++) {
for (int c2 = c1; c2 < m; c2++) {
// 使用前缀和快速计算子矩阵和
int sum = prefixSum[r2+1][c2+1] - prefixSum[r1][c2+1]
- prefixSum[r2+1][c1] + prefixSum[r1][c1];
maxSum = Math.max(maxSum, sum);
}
}
}
}
System.out.println(maxSum);
}
}
Python实现
python
def solve_prefix_sum():
n, m = map(int, input().split())
matrix = []
for _ in range(n):
row = list(map(int, input().split()))
matrix.append(row)
# 构建前缀和数组
prefix_sum = [[0] * (m + 1) for _ in range(n + 1)]
for i in range(1, n + 1):
for j in range(1, m + 1):
prefix_sum[i][j] = (matrix[i-1][j-1] + prefix_sum[i-1][j] +
prefix_sum[i][j-1] - prefix_sum[i-1][j-1])
max_sum = float('-inf')
# 枚举所有可能的子矩阵
for r1 in range(n):
for c1 in range(m):
for r2 in range(r1, n):
for c2 in range(c1, m):
# 使用前缀和快速计算子矩阵和
current_sum = (prefix_sum[r2+1][c2+1] - prefix_sum[r1][c2+1] -
prefix_sum[r2+1][c1] + prefix_sum[r1][c1])
max_sum = max(max_sum, current_sum)
print(max_sum)
solve_prefix_sum()
C++实现
cpp
#include <iostream>
#include <vector>
#include <climits>
#include <algorithm>
using namespace std;
int main() {
int n, m;
cin >> n >> m;
vector<vector<int>> matrix(n, vector<int>(m));
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
cin >> matrix[i][j];
}
}
// 构建前缀和数组
vector<vector<int>> prefixSum(n + 1, vector<int>(m + 1, 0));
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
prefixSum[i][j] = matrix[i-1][j-1] + prefixSum[i-1][j] +
prefixSum[i][j-1] - prefixSum[i-1][j-1];
}
}
int maxSum = INT_MIN;
// 枚举所有可能的子矩阵
for (int r1 = 0; r1 < n; r1++) {
for (int c1 = 0; c1 < m; c1++) {
for (int r2 = r1; r2 < n; r2++) {
for (int c2 = c1; c2 < m; c2++) {
// 使用前缀和快速计算子矩阵和
int sum = prefixSum[r2+1][c2+1] - prefixSum[r1][c2+1] -
prefixSum[r2+1][c1] + prefixSum[r1][c1];
maxSum = max(maxSum, sum);
}
}
}
}
cout << maxSum << endl;
return 0;
}
算法复杂度分析
解法一:暴力枚举法
- 时间复杂度:O(N²M²(N+M)),六重循环
- 空间复杂度:O(1)
解法二:Kadane算法优化
- 时间复杂度:O(N²M),三重循环
- 空间复杂度:O(M)
解法三:前缀和优化
- 时间复杂度:O(N²M²),四重循环但每次计算子矩阵和为O(1)
- 空间复杂度:O(NM)
示例分析
对于给定的示例:
3 4
-3 5 -1 5
2 4 -2 4
-1 3 -1 3
最大子矩阵是后面3列的所有元素:
5 -1 5 (第1行后3列)
4 -2 4 (第2行后3列)
3 -1 3 (第3行后3列)
和为:5 + (-1) + 5 + 4 + (-2) + 4 + 3 + (-1) + 3 = 20
总结
三种解法各有特点:
- 暴力枚举法:思路简单直观,但时间复杂度较高
- Kadane算法优化:是最优解法,时间复杂度最低,推荐使用
- 前缀和优化:在某些场景下有优势,特别是需要多次查询时
对于这道题目,由于N, M ≤ 10,数据规模较小,三种方法都能通过。但在实际应用中,Kadane算法优化是最佳选择,它将二维问题巧妙地转化为一维问题,大大降低了时间复杂度。