最大子矩阵和[前缀和->连续区间的O(1)求和 || 双指针去无意义的负数]

前言

预处理–前缀和,为解题赋能。计算区间由O(n) ->O(1);双指针滑动窗口+变量记录想要的解。

一、最大子矩阵和

在这里插入图片描述

二、二维前缀和+双指针

package com.xhu.offer.everyday;

import java.beans.beancontext.BeanContext;

//最大子矩阵
public class GetMaxMatrix {
    /*
    target:找所有子矩阵中最小的子矩阵和的左上行列坐标和右下行列坐标。
    如何找子矩阵?选两列选两行,围成一个子矩阵。m行n列,所以有m * m种行的取法,n * n种列的取法。即O(m*m*n*n)时间复杂度。
    如何得到子矩阵的左上行列和右下行列?((第一行,第一列),(第二行,第二列))。
    如何得到最小的子矩阵?
    1-矩阵直接求和,但缺点太明显。
    2-二维前缀和来O(1)求得子矩阵和。
    如何求二维前缀和?从第一行开始遍历,当前位置的二维前缀和 = 上面的前缀和 + 左边的前缀和 - 重复计算左上块和 + 自己。
    如何利用前缀和来求子矩阵的和?sum = 右下前缀和 - (左下列-1的前缀和)-(右上行-1的前缀和) + (多减去的左上行列减一的前缀和)
    M1:Pre-二维前缀和 + 暴力取子矩阵。
     */
    public int[] getMaxMatrix(int[][] matrix) {
        int m = matrix.length, n = matrix[0].length;
        //1-预处理前缀和
        int[][] prefix = getPrefixSum(matrix, m, n);
        //2-暴力求解
        int[] rs = {0, 0, 0, 0};
        int max = 1 << 31;
        for (int i = 0; i < m; i++) {
            for (int j = i; j < m; j++) {
                for (int k = 0; k < n; k++) {
                    for (int l = k; l < n; l++) {
                        int v1 = prefix[j][l];
                        int v2 = k == 0 ? 0 : prefix[j][k - 1];
                        int v3 = i == 0 ? 0 : prefix[i - 1][l];
                        int v4 = i == 0 || k == 0 ? 0 : prefix[i - 1][k - 1];
                        int sum = v1 - v2 - v3 + v4;
                        if (sum > max) {
                            max = sum;
                            rs[0] = i;
                            rs[1] = k;
                            rs[2] = j;
                            rs[3] = l;
                        }
                    }
                }
            }
        }
        //返回行列坐标
        return rs;
    }

    private int[][] getPrefixSum(int[][] matrix, int m, int n) {
        int[][] prefix = new int[m][n];

        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                int v1 = i == 0 ? 0 : prefix[i - 1][j];//上面前缀和。
                int v2 = j == 0 ? 0 : prefix[i][j - 1];//左面前缀和。
                int v3 = i == 0 || j == 0 ? 0 : prefix[i - 1][j - 1];//上面和左面的公共部分。
                //计算当前位置前缀和
                prefix[i][j] = v1 + v2 - v3 + matrix[i][j];
            }
        }
        return prefix;
    }

}

//方法一超时
class GetMaxMatrix2 {
    /*
    target:找所有子矩阵中最小的子矩阵和的左上行列坐标和右下行列坐标。
    如何找子矩阵?选两列选两行,围成一个子矩阵。m行n列,所以有m * m种行的取法,n * n种列的取法。即O(m*m*n*n)时间复杂度。
    如何得到子矩阵的左上行列和右下行列?((第一行,第一列),(第二行,第二列))。
    如何得到最小的子矩阵?
    1-矩阵直接求和,但缺点太明显。
    2-二维前缀和来O(1)求得子矩阵和。
    如何求二维前缀和?从第一行开始遍历,当前位置的二维前缀和 = 上面的前缀和 + 左边的前缀和 - 重复计算左上块和 + 自己。
    如何利用前缀和来求子矩阵的和?sum = 右下前缀和 - (左下列-1的前缀和)-(右上行-1的前缀和) + (多减去的左上行列减一的前缀和)
    M1:Pre-二维前缀和 + 暴力取子矩阵。
    M1:超时。
    可否改进?题目还有条件:有负数的存在,所以可去掉无意义的前缀部分即sum<=0的前缀部分。
    所以当行固定时,可确定第一列的位置,使得不加上负数前缀和。代码体现为sum<=0时,不要左边的前缀部分即重设第一列,来将复杂度降到O(m*m*n)
     */
    public int[] getMaxMatrix(int[][] matrix) {
        int m = matrix.length, n = matrix[0].length;
        //1-预处理前缀和
        int[][] prefix = getPrefixSum(matrix, m, n);
        //2-暴力求解
        int[] rs = {0, 0, 0, 0};
        int max = 1 << 31;
        for (int i = 0; i < m; i++) {
            for (int j = i; j < m; j++) {
                int k = 0;
                for (int l = 0; l < n; l++) {
                    int v1 = prefix[j][l];
                    int v2 = k == 0 ? 0 : prefix[j][k - 1];
                    int v3 = i == 0 ? 0 : prefix[i - 1][l];
                    int v4 = i == 0 || k == 0 ? 0 : prefix[i - 1][k - 1];
                    int sum = v1 - v2 - v3 + v4;

                    if (sum > max) {
                        max = sum;
                        rs[0] = i;
                        rs[1] = k;
                        rs[2] = j;
                        rs[3] = l;
                    }
                    if (sum <= 0) {
                        k = l + 1;
                    }
                }
            }
        }
        //返回行列坐标
        return rs;
    }

    private int[][] getPrefixSum(int[][] matrix, int m, int n) {
        int[][] prefix = new int[m][n];

        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                int v1 = i == 0 ? 0 : prefix[i - 1][j];//上面前缀和。
                int v2 = j == 0 ? 0 : prefix[i][j - 1];//左面前缀和。
                int v3 = i == 0 || j == 0 ? 0 : prefix[i - 1][j - 1];//上面和左面的公共部分。
                //计算当前位置前缀和
                prefix[i][j] = v1 + v2 - v3 + matrix[i][j];
            }
        }
        return prefix;
    }
    //[[9,-8,1,3,-2],[-3,7,6,-2,4],[6,-4,-4,8,-7]]

}

总结

1)常见题型:前缀和,这里作为里预处理组件,配合其它component来解题,所以是个困难题,毕竟要求单个知识的扎实掌握和多个扎实知识的逻辑组合性、连贯性。
2)常见的双指针,双指针作用很多,核心就是通过改变左右窗口端来使数据遍历两遍,从而将时间复杂度降到O(n)。
注:1-双指针一般配合max或min变量记录多个窗口中想要的窗口及窗口转化的结果;2-保存连续子数组问题对双指针的敏感性。

参考文献

[1] LeetCode 最大子矩阵和

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值