【华为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,和最大

解题思路

这是一个经典的最大子矩阵和 问题,是最大子数组和问题在二维空间的扩展。

核心思想:

  1. 枚举所有可能的上下边界
  2. 对于每一对上下边界,将问题转化为一维的最大子数组和问题
  3. 使用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

总结

三种解法各有特点:

  1. 暴力枚举法:思路简单直观,但时间复杂度较高
  2. Kadane算法优化:是最优解法,时间复杂度最低,推荐使用
  3. 前缀和优化:在某些场景下有优势,特别是需要多次查询时

对于这道题目,由于N, M ≤ 10,数据规模较小,三种方法都能通过。但在实际应用中,Kadane算法优化是最佳选择,它将二维问题巧妙地转化为一维问题,大大降低了时间复杂度。

相关推荐
努力学习的小廉3 小时前
深入了解linux系统—— 线程同步
linux·服务器·数据库·算法
数据爬坡ing3 小时前
从挑西瓜到树回归:用生活智慧理解机器学习算法
数据结构·深度学习·算法·决策树·机器学习
luoganttcc3 小时前
小鹏汽车 vla 算法最新进展和模型结构细节
人工智能·算法·汽车
wallflower20205 小时前
滑动窗口算法在前端开发中的探索与应用
前端·算法
林木辛5 小时前
LeetCode热题 42.接雨水
算法·leetcode
MicroTech20255 小时前
微算法科技(NASDAQ: MLGO)采用量子相位估计(QPE)方法,增强量子神经网络训练
大数据·算法·量子计算
星梦清河5 小时前
宋红康 JVM 笔记 Day15|垃圾回收相关算法
jvm·笔记·算法
货拉拉技术5 小时前
揭秘语音交互的核心技术
算法
矛取矛求6 小时前
日期类的实现
开发语言·c++·算法