动态规划学习(后期也会补充)

    动态规划总结(Dynamic Programming)
1.为什么要有动态规划?
2.什么是动态规划?
    是一种分阶段求解策略,简答的总结就是大事化小,小事化了。
3.动态规划具体的执行步骤是什么?
    动态规划包括三个部分:最优子结构、边界和状态转移方程。
    
4.动态规划的应用
    1)青蛙跳台阶问题:一只青蛙一次能跳一个台阶,也可以一次跳两个台阶,问:跳上n个台阶总共有多少种跳法?(假设n=10)
        分析过程:
        问题:1.假设你只剩下最后一步了,那么共有多少种走法呢?
                答:因为只剩下最后一步,要走到第十个台阶,那么当前的位置必须是位于第8个台阶上或者是第9个台阶上,最后一步才能跨上第十个台阶。
              2.假设0到第8个台阶共有 X种走法,0到第9个台阶共有Y中走法,那么0到10级台阶总共有多少种走法?
                答:10级台阶的分类可以按照最后一步的不同可以分为两种情况,第一部分是最后一步从9阶到10阶,第二部分是最后一步是8阶到10阶,那么总共
                的情况就是X+Y;于是可以总结到   0-10个台阶上走法数量=0-9个台阶上走法数量+0-8个台阶上的走法数量,记为F(10)=F(9)+F(8),
              3.那么如何分析F(8)和F(9)呢?
                答:可以根据上面类似的方法进行分析,按照最后一步的不同进行分析结果。
              4.结合上面的问题分析最优子结构、边界和状态转移方程
                答:最优子结构:从上面的的分析可知,F(10)=F(9)+F(8),那么F(9)和F(8)即是F(10)的最优子结构;
                    边界:由条件可知,上面的青蛙跳一步共有两种可能,即一步跳一个台阶和一步跳两个台阶,那么可以分析得:F(1)=1,F(2)=2;这就是边界
                            如果问题没有边界,那么就无法得到有限的结果。
                    状态转移方程:在本题中,分析可得:F(n)=F(n-1)+F(n-2)是阶段与阶段之间的状态转移方程;这是动态规划的核心,决定了问题的每个阶段与下个阶段之间的关系。
              
              总结:前面的1 2 3 4部完成了动态规划的前半部分,就是问题的建模,还有另外一部分,称为问题的求解。
              
              5.简单的分析一下,可以直接采用递归求解,递归的坏处这里不解释。遇到的问题:算了好多好多个重复的子问题
              
              6.利用备忘录算法解题:怎么避免每次都计算重复的结果呢?我们可以利用缓存技术,将已经算出的结果保存到一个Hash表中,后面如果是遇到相同的结果,那么
                就直接从hash表里面取数据。这就是备忘录算法
    备忘录算法:
    int getClimbingWays(int n,HashMap<Integer,Integer> map){
        if(n<1)
            return 0;
        else if(n==1)
            return 1;
        else if(n==2)
            return 2;
        if(map.contains(n))
            return map.get(n);
        else{
            int value=getClimbingWays(n-1)+getClimbingWays(n-2);
            map.put(value);
            return value;
        }
    }
    时间复杂度和空间复杂度:O(n)
    
    时间复杂度已经不能再继续优化了,那么如何将空间复杂度进行优化呢?   我们可以自底向下,用迭代的方式推导出结果,因为第三个状态只与第一二个状态相关,那么我们
    可以采用迭代的方法,来计算求解
    int getClimbingWays(int n){
        if(n<1)
            return 0;
        if(n<3)
            return n;
        int a=1;
        int b=2;
        int temp=0;
        for(int i=3;i<=n;i++){
            temp=a+b;
            a=b;
            b=temp;
        }
        return temp;
    }
    
    2)国王和金矿问题:有一个国家发现了5座金矿,每座金矿的黄金储量不同,需要参与挖掘的工人数也不同。参与挖矿工人的总数是10.每座金矿要么全挖,要么不挖。
        要求用程序解出,要想得到尽可能多的黄金,应该选择挖哪几座金矿?
        金矿1:400金/5人
        金矿2:500金/5人
        金矿3:200金/3人
        金矿4:300金/4人
        金矿5:350金/3人
        
        将问题便于描述:将金矿数量设置为N,工人数设置为W,金矿的黄金量设置为G[],金矿的用工量设置为P[]。
        
        问题分析:
        1.首先思考:如何转化为子问题?即如何确定最优子结构?
            答:第五个金矿存在挖与不挖的选择,总共两种情况:
                第一种情况:如果选择不挖:那么最优子结构就是10个工人挖4个金矿时,能挖出黄金的总数;
                第二种情况:如果选择挖:那么最优子结构就是(10-3)个工人挖前4个金矿,能挖出黄金的总数;
            最终问题就是10个人在5座金矿的条件下,如何挖出最多的黄金
        2.那么最终问题和最优子结构之间有什么关系呢?换句话说,4个金矿的最优选择和5个金矿的最优选择有什么关系呢?(关键点)
            答:5个金矿的最优选择  就是(前4座金矿10工人的挖金数量)和(前4座金矿7工人的挖金数量+第5座金矿的挖金数量)的最大值!
                那么5座金矿和4座金矿的最优选择之间存在这样的关系:F(5,10)=MAX(F(4,10),F(4,10-P[4])+G(4))
        3.如何确定问题的边界条件?
            答:边界自然是只有1座金矿,也就是N=1的时候,只能挖这一座金矿:
                当工人的数量足够去挖这个金矿的时候,那么得到的黄金数量为G(0)
                当工人的数量不够去挖这个金矿的时候,那么得到的黄金数量就是0;
        
        整理得到状态的转移方程;
            F(n,w)=0; (n<=1,w<p[0])
            F(n,w)=g[0];(n==1,w>=p[0])
            F(n,w)=F(n-1,w);(n>1,w<p[n-1])
            F(n,w)=max(F(n-1,w),F(n-1,w-p[n-1])+g[n-1]);(n>1,w>=p[n-1])
            
        到了这一步,问题的建模就已经完成,那么我们还是要解决问题
        
        同样的有三种解决方式
        
        方法1:递归(具体实现)
        
        方法2:备忘录算法:在简单递归的基础山增加一盒HashMap备忘录,用来存储中间结果。HashMap 的Key是一个包含金矿工人数N和工人数W的对象,Value是最优选择获得的
        黄金数。(具体实现)
        
        方法3:动态规划分析
            通过画表格分析
                1工人    2工人    3工人    4工人    5工人    6工人    7工人    8工人    9工人    10工人
        1金矿    0        0        0        0        400        400        400        400        400        400
        2金矿    0        0        0        0        500        500        500        500        500        900
        3金矿    0        0        200        200        500        500        500        700        700        900
        4金矿    0        0        200        300        500        500        500        700        800        900
        5金矿    0        0        350        350        500        550        650        850        850        900
    
        分析表格发现规律:除了第一行以外,每个格子都是前一行的一个或两个格子推导而来:
            如3金矿8工人的结果:来自于2金矿5工人和2金矿8工人,MAX(500,500+200)
            再比如5金矿10工人的结果,就来自于4金矿7工人和4金矿10工人,MAX(900,500+350)=900
        
        于是,我们写程序的时候也可以这样从左到由,从上到下一格一格的推导出最终结果,而且我们不需要存储整个表格,只需要存储前一行的结果,就可以推导出新的一行
        Code:
        int getMostGold(int n,int w,int []g,int []p){
            int []preResult=new int[p.length];
            int []result=new int[p.length];
            
            //填充边界格子的值
            for(int i=0;i<=n;i++){
                if(i<p[0])
                    preResult[i]=0;
                else
                    preResult[i]=g[0];
            }
            
            //填充其余格子的值,外层循环是金矿数量,内存循环是人工数
            for(int i=0;i<n;i++)
                for(int j=0;j<=w;j++){
                    if(j<p[i])
                        result[j]=preResult[j];
                    else
                        result[j]=Math.max(preResult[j],preResult[j-p[i]]+g[i]);
                preResult=result;
                }
            return result[n];
        }
        
        3)变态青蛙题分析:一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
            关于本题,前提是n个台阶会有一次n阶的跳法。分析如下:
            f(1) = 1
            f(2) = f(2-1) + f(2-2)         //f(2-2) 表示2阶一次跳2阶的次数。
            f(3) = f(3-1) + f(3-2) + f(3-3) 
            ...
            f(n) = f(n-1) + f(n-2) + f(n-3) + ... + f(n-(n-1)) + f(n-n) 
说明: 
1)这里的f(n) 代表的是n个台阶有一次1,2,...n阶的 跳法数。
2)n = 1时,只有1种跳法,f(1) = 1
3) n = 2时,会有两个跳得方式,一次1阶或者2阶,这回归到了问题(1) ,f(2) = f(2-1) + f(2-2) 
4) n = 3时,会有三种跳得方式,1阶、2阶、3阶,
    那么就是第一次跳出1阶后面剩下:f(3-1);第一次跳出2阶,剩下f(3-2);第一次3阶,那么剩下f(3-3)
    因此结论是f(3) = f(3-1)+f(3-2)+f(3-3)
5) n = n时,会有n中跳的方式,1阶、2阶...n阶,得出结论:
    f(n) = f(n-1)+f(n-2)+...+f(n-(n-1)) + f(n-n) => f(0) + f(1) + f(2) + f(3) + ... + f(n-1)
6) 由以上已经是一种结论,但是为了简单,我们可以继续简化:
    f(n-1) = f(0) + f(1)+f(2)+f(3) + ... + f((n-1)-1) = f(0) + f(1) + f(2) + f(3) + ... + f(n-2)
    f(n) = f(0) + f(1) + f(2) + f(3) + ... + f(n-2) + f(n-1) = f(n-1) + f(n-1)
    可以得出:
    f(n) = 2*f(n-1)
7) 得出最终结论,在n阶台阶,一次有1、2、...n阶的跳的方式时,总得跳法为:
              | 1       ,(n=0 ) 
      f(n) =  | 1       ,(n=1 )
              | 2*f(n-1),(n>=2)

4)最大的连续子序列的和:给一个数组,返回它的最大连续子序列的和,例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。
    对于本题:确定如何实现DP是关键。
    首先确定变量的含义:
    F(i):以array[i]为末尾元素的子数组的和的最大值,子数组的元素的相对位置不变
        F(i)=max(F(i-1)+array[i] , array[i])
    res:所有子数组的和的最大值,计算之后,每一步都要取最大值
        res=max(res,F(i))

    如数组[6, -3, -2, 7, -15, 1, 2, 2]
    初始状态:
    F(0)=6
    res=6
    i=1:
        F(1)=max(F(0)-3,-3)=max(6-3,3)=3
        res=max(F(1),res)=max(3,6)=6
    i=2:
        F(2)=max(F(1)-2,-2)=max(3-2,-2)=1
        res=max(F(2),res)=max(1,6)=6
    i=3:
        F(3)=max(F(2)+7,7)=max(1+7,7)=8
        res=max(F(2),res)=max(8,6)=8
    i=4:
        F(4)=max(F(3)-15,-15)=max(8-15,-15)=-7
        res=max(F(4),res)=max(-7,8)=8
    以此类推
    最终res的值为8
    关于本题的三种解答方式:
     public int FindGreatestSumOfSubArray(int[] array){
        if (array.length==0||array==null){
            return 0;
        }
        //将greatest设置为最小的int型的数
        int greatest=0x80000000;
        int currentSum=0;
        for (int i=0;i<array.length;i++){
            //如果前面子数组的和小于0,则放弃前面的连续子数组
            if(currentSum<=0){
                currentSum=array[i];
            }else{
                currentSum+=array[i];
            }
            if(currentSum>greatest){
                greatest=currentSum;
            }
        }
        return greatest;
    }
    /**
     * use DP
     * */
    public int FindGreatestSumOfSubArray1(int[] array){
        if (array.length==0||array==null){
            return 0;
        }
        int []F=new int[array.length];
        F[0]=array[0];
        int res=array[0];
        for(int i=1;i<array.length;i++){
            F[i]=Math.max(F[i-1]+array[i],array[i]);
            res=Math.max(F[i],res);
        }
        return res;
    }
    /**
     * 某位大佬的解答
     * */
    public  int FindGreatestSumOfSubArray3(int[] array) {
        //记录当前所有子数组的和的最大值
        int res = array[0];
        //包含array[i]的连续数组最大值
        int max=array[0];
        for (int i = 1; i < array.length; i++) {
            max=Math.max(max+array[i], array[i]);
            res=Math.max(max, res);
        }
        return res;
    }

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值