动态规划中的降维之逆序遍历

降维

动态规划类的题目,如果题目里给了两个参数,那么往往就需要建立二维动态数组,在做题的时候要随手把表格画出来,便于理解。

很多时候,二维数组可以进行空间优化,因为第i行的值往往只与第i-1行有关,因此只需要维护一个一维的动态数组就好了。

在维护一个一维dp数组的时候,有时候第i个元素的新的值,只跟第i个元素的旧值,以及它前面那个值有关,这样的话就不需要逆序遍历。

例题:1、leetcode 64 : 最小路径和 https://leetcode-cn.com/problems/minimum-path-sum/

例题:2、leetcode 62 : 不同路径 https://leetcode-cn.com/problems/unique-paths/

这两个题都是动态规划类题,都可以将二维的动态空间优化成一维,而且都只需要顺序遍历就好。

降维之逆序遍历

有些题目将二维动态数组优化成一维dp数组之后,第i个元素的新值需要用到第0~i-1所有的值综合计算得出,此时如果继续顺序遍历,那么就会覆盖掉dp数组的旧值,导致后面无法再继续计算。

所谓新值和旧值:

例如第k次遍历之后,dp数组的值为{1、2、3、4、5、6},这就是旧值,在进行第k+1次遍历时要用旧值获取新值,例如计算第4个数的新值,要用到前3个数的旧值。而如果顺序遍历的话,当计算第4数时,前3个值已经是新值了,就无法得出结果。

所以此时要进行逆序遍历,用前5个数的旧值,计算第6个数的新值,再用前4个数旧值,计算第5个数新值,以此类推,不会再发生覆盖的情况。

例题:leetcode #813 最大平均值的分组

https://leetcode-cn.com/problems/largest-sum-of-averages/

我们将给定的数组 A 分成 K 个相邻的非空子数组 ,我们的分数由每个子数组内的平均值的总和构成。计算我们所能得到的最大分数是多少。

示例:
输入:  A = [9,1,2,3,9]   K = 3
输出: 20

就是将数组A完全分配成K组,对各组分别求平均数,然后求和,获得各种分配方式的最大值。

题目涉及到两个元素,先用二维动态规划做,后面再考虑空间优化。

建立二维动态数组dp,dp[i][j]表示,前i个数分配成j组的结果值。

那么动态规划方程可以表示为:

dp[i][j] = max(dp[1][j-1]+SUM(2~i)/(i-1),...dp[x][j-1]+SUM(x+1~i)/(i-x),...dp[i-1][j-1]+SUM(i)/(1))
 
 

dp[x][j-1]+SUM(x+1~i)/(i-x)的含义是,将前x个数分成j-1组,x+1~i这些数分成另外一组,一共j组,前j-1组的结果用dp[x][j-1]表示,后面另外一组的结果是x+1i所有值的和除以个数。

我们发现分成j组的情况只跟分成j-1组的结果有关,因此就可以将二维的动态数组降维到一维。降到一维之后就意识到我们前面提到的是顺序遍历还是逆序遍历的问题。dp数组里的第i个值的新值由旧值中1~i-1所有的值计算得来 ,因此不能顺序遍历,要逆序遍历。

答案代码:1、未优化之前,二维数组

class Solution {
public:
    double largestSumOfAverages(vector<int>& A, int k) {
 
        int N = A.size();//记录个数
 
//新建一个长度为N+1的数组,第i位表示,前i个数的总和,注意这里类型要设置成double,否则后面计算除法sum[i] / i时默认结果为整形
//这个bug困扰了我两个小时,在本地IDE打印dp数组,才发现怎么全是整数
        vector<double>sum(N + 1,0);
        for(int i = 0;i < N;i++)
        {
            sum[i + 1] = sum[i] + A[i];
        }
 
//新建一个二维dp数组,注意这里是N+1,k+1,dp[i][j]就表示,前i个数分成j组的最大值
        vector<vector<double>>dp(N + 1,vector<double>(k + 1,0.0));
        for(int i = 1;i <= N;i++)
        {
            dp[i][1] = sum[i] / i;//j等于1直接就表示所有值都在一组
            for(int j = 2;j <= i && j <= k;j++)
            {
                for(int x = 1;x < i;x++)
                {
                     dp[i][j] = max( dp[i][j] , dp[x][j-1] + (sum[i] - sum[x]) / (i - x) );
                }
            }
        }
        return dp[N][k];
    }
};

答案代码:2、优化之后

class Solution {
public:
    double largestSumOfAverages(vector<int>& A, int k) {
        int N = A.size();//记录个数
        vector<double>sum(N + 1,0);
        vector<double>dp(N + 1,0.0);
        for(int i = 0;i < N;i++)
        {
            sum[i + 1] = sum[i] + A[i];
            dp[i + 1] = sum[i+1]/(i+1);
        }
        
        for(int i = 2;i <= k;i++)
        {
            for(int j = N;j > 0;j--)//逆序,逆序,逆序
            {
                for(int s = 1;s < j;s++)
                {
                     dp[j] = max( dp[j] , dp[s] + (sum[j] - sum[s]) / (j - s) );
                }
            }
        }
        return dp[N];
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值