【华为OD】最大子矩阵和
【华为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实现
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实现
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 = 0for 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++实现
#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实现
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实现
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_fardef 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] * mfor 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++实现
#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实现
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实现
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++实现
#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 52 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算法优化是最佳选择,它将二维问题巧妙地转化为一维问题,大大降低了时间复杂度。