动态规划

动态规划中包含三个重要概念:最优子结构、边界、状态转移公式。
题目一上台阶问题
(1)问题建模
F(10)=F(9)+F(8);
F(9)和F(8)是F(10)的最优子结构;
F(1)和F(2)是问题的边界,如果一个问题没有边界将永远无法得到有限的结果。
F(n)=F(n-1)+F(n-2)是阶段与阶段之间的【状态转移方程】。这是动态规划的核心,决定了问题的每一个阶段和下一阶段的关系。
(2)求解问题
法一:递归求解

  int getWays(int n){
   if(n<1){
      return 0;
   }
   if(n==1){
      return 1;
   }
   if(n==2){
      return 2;
   }
   return getWays(n-1)+getWays(n-2);
   }

时间复杂度分析:
在这里插入图片描述
树的节点个数就是递归方法所需要计算的次数。
不能难看出,这颗二叉树的高度是N-1,节点个数接近2的N-1次方。所以时间复杂度可以近似地看作是O(2^N)。

优化,避免重复计算
方法二:备忘录算法

int getWays(int n,HashMap<Integer,Integer> map){
if(n<1){
    return 0;
       }
 if(n==1){
   return 1;
      }
 if(n==2){
   return 2;
      }
 if(map.contains(n)){
   return map.get(n);
     }else{
    int value =getWays(n-1)+getWays(n-2);
    map.put(n,value);
    return value;
         }
    }
}

在以上代码中,集合map是一个备忘录,当每次需要计算F(N)的时候,会首先从map中寻找匹配元素,如果map中存在,就直接返回结果,如果map中不存在,就计算出结果,存入备忘录中。

时间复杂度分析:从F(1)到F(N)一共有N个不同的输入,在哈希表里存了N-2个结果,所以时间复杂度和空间复杂度都是O(N)。

继续优化,缩小空间复杂度。
备忘录是自顶向下做递归,动态规划是自底向上迭代
方法三:动态规划
每一次迭代过程中,只要保留之前的两个状态,就可以推导出新的状态,而不需要像备忘录算法那样保留全部的子状态。

int getWays(int n){
   if(n<1){
     return 0;
   }
   if(n==1){
     return 1;
   }
   if(n==2){
     return 2;
   }
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;
}

时间复杂度为O(n),空间复杂度O(1)

题目二:国王和金矿
有一个国家发现了5座金矿,每座金矿的黄金储量不同,需要参与挖掘的工人数也不同。参与挖矿工人的总数是10人。每座金矿要么全挖,要么不挖,不能派出一半人挖取一半金矿。要求用程序求解出,要想得到尽可能多的黄金,应该选择挖取哪几座金矿?

总共10名工人
200金/3人 300金/4人 350金/3人
400金/5人 500金/5人

方法一:排列组合
每一座金矿都有挖与不挖两种选择,如果有N座金矿,排列组合起来有2^N种选择。对所有可能性做遍历,排除那些使用工人数超过10的选择,在剩下的选择里找出获得金币数最多的选择。

(1)问题建模
动态规划有三个核心元素:最优子结构、边界、状态转移方程式。
最优子结构:有两个。一,当第五个金矿选择不挖时,最优子结构应该是10个工人4个金矿时,挖出最多黄金的选择。二,当第五个金矿选择挖,会占用掉一部分工人,那么前四个金矿所分配的工人数量就是【10-第5个金矿所需人数】,而不再是10。
最优子结构和最终结果的关系
为了便于描述,把金矿数量设为N,工人数设为W,金矿的黄金量设为数组G【】,金矿的用工量设为数组P【】。
那么5座金矿和4座金矿的最优选择之间存在这样的关系:F(5,10)=
MAX(F(4,10),F(4,10-P[4]) + G[4])
边界
两种情况:
只有一座金矿,当工人数量够挖取第一座金矿时,得到的黄金数就是G【0】
当工人数量不够挖取第一座金矿时,得到的金矿数黄金数为0.
用公式来表达:
当N=1,W>=P[0],F(N,W)=G[0];
当N=1,W<P[0] 时,F(N,W)=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])

(2)实现方法:简单递归、备忘录算法、动态规划

方法二:简单递归
把状态转移方程式翻译成递归程序,递归的结束的条件就是方程式当中的边界。因为每个状态有两个最优子结构,所以递归的执行流程类似于一颗高度为N的二叉树。时间复杂度为O(2^N).

方法三:备忘录算法
在简单递归的基础上增加一个HashMap备忘录,用来存储中间结果。HashMap的Key是一个包含金矿数N和工人数W的对象,Value是最优选择获得的黄金数。在这里插入图片描述
规律:除了第1行以外,每个格子都是前一行的一个或两个格子推导而来。
比如3金矿8工人的结果,就来自与2金矿8工人和2金矿5工人加3金矿3工人的最大值,Max(500,500+200)=700.

用程序实现的时候,也可以像这样从左至右,从上到下一格一格推导出最终结果。而且不需要存储整个表格,只需要存储前一行的结果,就可以推导出新的一行
方法四:动态规划
在这里插入图片描述
方法利用两层迭代,来逐步推导出最终结果。在外层的每一次迭代,也就是对表格每一行的迭代过程中,都会保留上一行的结果数组preResults,并循环计算当前行的结果数组results。

方法的时间复杂度是O(n*w),空间复杂度是(w).需要注意的是,金矿只有5座的时候,动态规划的性能优势还未显示出来。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值