最大子矩阵 最大子数组 动态规划

最大子矩阵 最大子数组 动态规划

  • 最大子矩阵问题也是动态规划中经典的一道题目(感觉自己到目前为止接触到的只是动态规划的冰山一角,区间dp、数位dp… 还是要花时间深入学习一下的),这里就先总结一下最大子矩阵问题的通用思路以及解法

  • 我们先从基础难度讲起,不直接讨论二维矩阵的情况,先来看看一维数组:

    • 现假设有一个一维数组 arr[n],要你找出连续的一段数组元素,使其和最大

    • 例如,一个一维数组:4 -5 6 3 7 -1 8 ,通过肉眼观察, 6 3 7 -1 8 为这个数组的连续最大和。那么有什么算法能够解决这个问题呢?

  • 1、穷举(暴力)

    int max_sum, tmp_sum;
    for(int i = 0, i < n; i++) {
    	for(int j = 0; j <= i; j++) {
    		tmp_sum = 0;
    		for(int k = j; k <= i; k++) {
    			tmp_sum += arr[k];
    		}
    		if (tmp_sum > max_sum)
    			max_sum = tmp_sum;
    	}
    }
    
    • 穷举法的思路很简单,就是列举数组 arr 的所有子集,计算每个子集的和,同时更新最大值。但该方法的时间复杂度为 O ( n 3 ) O(n^3) O(n3) ,显然在 n 较大时这方法行不通
  • 2、累加

    arr_sum[n + 10] = {0};
    for(i = 1; i <= n; i++) {
    	arr_sum[i] = arr_sum[i-1] + arr[i];
    }
    int max_sum = 0, tmp_sum;
    for(i = 1;i <= n; i++) {
    	for(j = 0; j < i; j++) {
    		tmp_sum = arr_sum[i] - arr_sum[j];
    		if(tmp_sum > max_sum)
    			max_sum = tmp_sum;
    	}
    }
    
    • 上述方法将时间复杂度优化到了 O ( n 2 ) O(n^2) O(n2) ,不过还可以继续优化,即使用动态规划的思想
  • 3、动态规划

    int get_MaxSum(int arr[], int col) {
        int max_sum = 0, tmp = 0;
        for (int i = 0; i < col; i++) {
            if (tmp > 0) {
                tmp += arr[i];
            }
            else {
                tmp = arr[i];
            }
            if (tmp > max_sum) {
                max_sum = tmp;
            }
        }
        return max_sum;
    }
    
  • 前面说了那么多一维数组求最大子段和的方法,这和最大子矩阵有什么关系呢?继续往下看

  • 最大子矩阵题目:给定一个 n*m 的矩阵 A,求 A 中的一个非空子矩阵,使这个子矩阵中的元素和最大。其中,A 的子矩阵指在 A 中行和列均连续的一块,例如:

    Example Input
    1
    3 3
    -1 -4 3
    3 4 -1
    -5 -2 8
    Example Output
    10
    样例说明:
    	取最后一列,和为10
    
  • 假设我们要求的最大子矩阵是原矩阵的第 i 行到第 j 行,第 k 列到第 s 列,那么如下图所示,圈出的范围即最大子矩阵。我们将最大子矩阵的每一列各自加和,就可以得到一个一维数组:{a[i][k] +······+ a[j][k], ······ , a[i][s] +······+ a[j][s]},那么现在,这就变成了一个一维数组求最大子段和的问题了!也就是说,将一个二维数组的纵向元素相加,变成一个横向上的一维数组,就能使用一维的方法来解决这个问题了

    在这里插入图片描述

    int n, m;
    int dp[maxn][maxn], tmp_arr[maxn];
    
    int get_MaxSum(int arr[], int col) {
        int max_sum = 0, tmp = 0;
        for (int i = 0; i < col; i++) {
            if (tmp > 0) {
                tmp += arr[i];
            }
            else {
                tmp = arr[i];
            }
            if (tmp > max_sum) {
                max_sum = tmp;
            }
        }
        return max_sum;
    }
    
    void solve() {
        int cas;
        scanf("%d", &cas);
        while (cas--) {
            scanf("%d%d", &n, &m);
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < m; j++) {
                    scanf("%d", &dp[i][j]);
                }
            }
            long long int max_sum = 0, tmp_sum;
            for (int i = 0; i < n; i++) {
                memset(tmp_arr, 0, sizeof(tmp_arr));
                for (int j = i; j < n; j++) {
                    for (int k = 0; k < m; k++) {
                        tmp_arr[k] += dp[j][k];
                    }
                    tmp_sum = get_MaxSum(tmp_arr, m);
                    if (tmp_sum > max_sum) {
                        max_sum = tmp_sum;
                    }
                }
            }
            cout<<max_sum<<endl;
        }
    }
    
  • 这里同样也可以使用累加的方法:

    void solve() {
    	int dp[maxn][maxn] = {0};
    	int n, m, tmp;
    	scanf("%d%d", &n, &m);
    	for (int i = 1; i <= n; i++) {
    	    for (int j = 1; j <= m; j++) {
    	        scanf("%d", &tmp);
    	        dp[i][j] = dp[i-1][j] + tmp;
    	    }
    	}
    	int tmp_sum = 0, max_sum = -999999;
    	for (int i = 1; i <= n; i++) {
    	    for (int j = i; j <= n; j++) {
    	        tmp_sum = 0;
    	        for (int k = 1; k <= m; k++) {
    	            tmp_sum += dp[j][k] - dp[i-1][k];
    	            if(tmp_sum > max_sum)
    	                max_sum = tmp_sum;
    	            if(tmp_sum < 0)
    	                tmp_sum = 0;
    	        }
    	    }
    	}
    	cout<<max_sum<<endl;
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JiangNanMax

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值