leetcode
leetcode 2851 ~ 2900
最大子矩阵

最大子矩阵

难度:

标签:

题目描述

Given an NxM matrix of positive and negative integers, write code to find the submatrix with the largest possible sum.

Return an array [r1, c1, r2, c2], where r1, c1 are the row number and the column number of the submatrix's upper left corner respectively, and r2, c2 are the row number of and the column number of lower right corner. If there are more than one answers, return any one of them.

Note: This problem is slightly different from the original one in the book.

Example:

Input:
[
   [-1,0],
   [0,-1]
]
Output: [0,1,0,1]

Note:

  • 1 <= matrix.length, matrix[0].length <= 200

代码结果

运行时间: 936 ms, 内存: 18.8 MB


/*
 * 思路:
 * 使用动态规划和Kadane算法来解决这个问题。
 * 这里使用Java Stream API实现。
 * 首先,将每一列的元素累加起来,然后使用Kadane算法在累加数组上找出最大子数组和。
 * 在找出最大子数组和的同时,记录对应的行索引。
 * 最后,通过累加的列索引和Kadane算法得到的行索引确定最大子矩阵的左上角和右下角。
 */

import java.util.stream.IntStream;

public class Solution {
    public int[] getMaxMatrix(int[][] matrix) {
        int row = matrix.length;
        int col = matrix[0].length;
        int[] result = new int[4];
        int maxSum = Integer.MIN_VALUE;
        
        for (int left = 0; left < col; left++) {
            int[] rowSum = new int[row];
            for (int right = left; right < col; right++) {
                IntStream.range(0, row).forEach(i -> rowSum[i] += matrix[i][right]);
                int[] tempResult = kadane(rowSum);
                if (tempResult[2] > maxSum) {
                    maxSum = tempResult[2];
                    result[0] = tempResult[0];
                    result[1] = left;
                    result[2] = tempResult[1];
                    result[3] = right;
                }
            }
        }
        return result;
    }
    
    private int[] kadane(int[] array) {
        int start = 0, end = 0, tempStart = 0, maxSum = array[0], sum = array[0];
        for (int i = 1; i < array.length; i++) {
            if (sum < 0) {
                sum = array[i];
                tempStart = i;
            } else {
                sum += array[i];
            }
            if (sum > maxSum) {
                maxSum = sum;
                start = tempStart;
                end = i;
            }
        }
        return new int[] {start, end, maxSum};
    }
}

解释

方法:

这个问题可以通过将二维问题转化为一维问题来解决。首先,我们通过计算矩阵的纵向前缀和来简化区间求和的操作。然后,通过固定子矩阵的上下界(top 和 bottom),将问题转换为求解固定行区间内部的最大子数组和,这一步使用类似Kadane算法的动态规划方法。对于每一对上下界,我们遍历所有可能的左右边界,计算这个矩形区间的和,并更新最大值。如果当前的累积和为负数,则重新开始计算新的子数组,否则继续累加。

时间复杂度:

O(H^2 * W)

空间复杂度:

O(H * W)

代码细节讲解

🦆
在转换二维问题为一维问题的过程中,你是如何选择纵向前缀和而不是横向前缀和?
在解决最大子矩阵问题时,选择纵向前缀和而不是横向前缀和的原因主要是由于我们需要处理和优化对任意子矩阵的求和操作。通过固定子矩阵的上下界(即行),我们可以将二维问题降维到一维问题,即在固定的行范围内寻找最大的子数组和。如果我们使用纵向前缀和,可以非常便捷地通过两个前缀和之差直接得到任意两行间的列元素和,这样我们就可以将问题简化为求解一维数组的最大子数组和。如果使用横向前缀和,虽然也可以处理,但在固定行的情况下对列的处理会变得复杂,不如纵向前缀和直接和高效。
🦆
纵向前缀和的计算为何从索引1开始而不是0?这样做有什么特殊的考虑吗?
纵向前缀和数组的索引从1开始而不是0的设计是为了方便计算和处理边界情况。在计算过程中,我们需要通过前缀和数组快速获取从第一行到任意第i行的累积和。如果从1开始,我们可以非常方便地通过prefixSum[bottom + 1][column] - prefixSum[top][column]来计算从第top行到第bottom行的和。若从0开始,则每次计算时都需要做额外的判断或调整,以确保不越界或错误地引用了不存在的索引-1的元素。因此,从1开始可以简化代码逻辑,避免边界问题。
🦆
在使用类似Kadane算法求解最大子数组和时,如果当前累积和为负数直接重置,这种情况下是否有可能错过最优解?
在Kadane算法中,当当前累积和变为负数时,重置累积和是基于这样的理念:一个负的累积和不会对求取最大子数组和产生帮助,因为任何包含这个负累积和部分的更大数组的总和都会比不包含这一部分的总和更小。因此,重置累积和并从下一个元素重新开始,不会错过最优解。这是算法确保能够找到最大子数组和的关键步骤,而不是一个可能导致漏解的问题点。

相关问题