动态规划练习

什么是动态规划:
1、本质就是利用申请的空间来记录每一个暴力搜索的计算结果,下次要用结果的时候直接使用,而不再进行重复的递归过程
2、动态规划每一中递归状态的计算顺序,依次进行计算。

面试中遇到暴力递归题目可以优化成动态规划方法的大体过程:
1、实现暴力递归方法
2、在暴力搜索方法的函数中看那些参数可以代表递归过程
3、找到代表递归过程的参数之后,记忆化搜索的方法非常容易实现。
4、通过分析记忆化搜索的依赖路径,从而实现动态规划
5、根据记忆化搜索方法改出动态规划的方法,进而看看能否能简化,如果能简化,还能实现时间复杂度更低的动态规划方法

动态规划方法的关键点:
1、最优化原理,也就是最优子结构性质,这是指一个最优化策略具有这样的性质,不论过去状态和决策如何,对前面的决策所形成的状态而言,余下的诸决策必须构成最优策略,简单来说就是一个最优化策略的子决策总是最优的,如果一个问题满足最优化原理,就称其具有最优子结构特征
2、无后效性,指在某状态下决策的收益,只与状态和决策相关,而与到达该状态的方式无关
3、子问题的重叠性,动态规划将指数级复杂度的暴力搜索算法,改进成了具有多项式时间复杂度的算法,关键在于解决冗余,这是动态规划算法的根本目的。

这个“最优化原理”如果用数学化一点的语言来描述的话,就是:假设为了解决某一优化问题,需要依次作出n个决策D1,D2,…,Dn,如若这个决策序列是最优的,对于任何一个整数k,1 < k < n,不论前面k个决策是怎样的,以后的最优决策只取决于由前面决策所确定的当前状态,即以后的决策Dk+1,Dk+2,…,Dn也是最优的。

自我感觉难点在于如何设计策略,有了策略后面的过程就很简单了。


有数组penny,penny中所有的值都为正数且不重复。每个值代表一种面值的货币,每种面值的货币可以使用任意张,再给定一个整数aim(小于等于1000)代表要找的钱数,求换钱有多少种方法。

给定数组penny及它的大小(小于等于50),同时给定一个整数aim,请返回有多少种方法可以凑成aim。

测试样例:
[1,2,4],3,3
返回:2


方法一:
暴力递归搜索算法:
假设arr={5,10,25,1}aim=1000;
1、用0张5元的货币,让{10,25,1}组成剩下的1000,最终方法记为res1;
2、用1张5元的货币,让{10,25,1}组成剩下的995,最终方法记为res2;
。。。。。
以此类推,并最终叠加就是结果,每一个单独的问题又可以进行迭代。代码如下:

class Exchange {
public:
    int countWays(vector<int> penny, int n, int aim) {
        return recurcount(penny,0,n,aim);
    }
    int recurcount(vector<int> penny,int begin,int n,int aim)
        {
        if(begin==n-1)
            {
            if(aim%penny[begin]==0)
                return 1;
            else
                return 0;
        }
        int sum=0;
        for(int i=0;i*penny[begin]<=aim;++i)
            {
            sum+=recurcount(penny,begin+1,n,aim-i*penny[begin]);
        }
        return sum;
    }
};

方法二:记忆搜索算法
暴力搜索之所以复杂度高,就是因为有很多重复计算,比如2张5元0张10元组成的和0张五元,1张10元组成的是相同的结果,在暴力搜索中计算了两次。所以可以通过一个全局变量进行记录,从而减少迭代的次数,加快程序的指向速度。或者想迭代函数传递一个数组保存计算过的值。代码如下:

class Exchange {
public:
    int a[60][1100];
    int countWays(vector<int> penny, int n, int aim) {
        memset(a,-1,sizeof(a));
        return recurcount(penny,0,n,aim);
    }
    int recurcount(vector<int> penny,int begin,int n,int aim)
        {
        if(begin==n-1)
            {
            if(aim%penny[begin]==0)
                return 1;
            else
                return 0;
        }
        int sum=0;
        for(int i=0;i*penny[begin]<=aim;++i)
            {
            if(a[begin+1][aim-i*penny[begin]]==-1)
                a[begin+1][aim-i*penny[begin]]=recurcount(penny,begin+1,n,aim-i*penny[begin]);
            sum+=a[begin+1][aim-i*penny[begin]];
        }
        return sum;
    }
};

方法三 未优化的动态规划

如果arr长度为N,生成行数为N,列数为aim+1的矩阵dp,dp[i][j]的含义是在使用arr[0….i]货币的情况下,组成钱数j有多少种方法
dp[i][j]=dp[i-1][j]+dp[i-1][j-1*arr[i]]+dp[i-1][j-2*arr[i]]+………

记忆搜索方法与动态规划方法的联系:
1、记忆化搜索方法就是某种形态的动态规划方法;
2 、记忆化搜索方法不关心到达某一个递归过程的路径,只是单纯的对计算过的递归过程进行记录,避免重复的递归过程
3、动态规划的方法则是规定好每一个递归过程的计算顺序,依次进行计算,后面的计算过程严格依赖前面的计算过程。
4、两者都是空间换时间的放大,也都有枚举的过程,区别就在于动态规划规定计算顺序,而即系搜索不用规定。

注意组成0元的方法也有1种,就是一个都没有

class Exchange {
public:

    int countWays(vector<int> penny, int n, int aim) {
        int dp[52][1003];
        memset(dp,0,sizeof(dp));
        for(int i=0;i<=aim;++i)
            {
            if(i%penny[0]==0)
            dp[0][i]=1;
        }
        for(int i=1;i<n;++i){
            for(int j=0;j<=aim;++j)
                {
                 for(int k=j;k>=0;k=k-penny[i])
                 dp[i][j]+=dp[i-1][k];
            }
        }
        return dp[n-1][aim];
    }

};

方法四:对上面的动态规划过程进行优化:
根据推导公式可得
dp[i][j]=dp[i-1][j]+dp[i-1][j-1*arr[i]]+dp[i-1][j-2*arr[i]]+………
dp[i][j-arr[i]]=dp[i-1][j-1*arr[i]]+dp[i-1][j-2*arr[i]]+………
所以
dp[i][j]=dp[i-1][j]+dp[i][j-arr[i]]
从而将时间复杂度由O(N*aim^2)降低到O(N*aim);

代码如下:

class Exchange {
public:

    int countWays(vector<int> penny, int n, int aim) {
        int dp[52][1003];
        memset(dp,0,sizeof(dp));
        for(int i=0;i<=aim;++i)
            {
            if(i%penny[0]==0)
            dp[0][i]=1;
        }
        for(int i=1;i<n;++i){
            for(int j=0;j<=aim;++j)
                {
                 int k=j;
                 if(k-penny[i]>=0)
                 dp[i][j]+=dp[i-1][k]+dp[i][k-penny[i]];
                else
                 dp[i][j]+=dp[i-1][k]; 
            }
        }
        return dp[n-1][aim];
    }

};
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值