微信公众号

题目:

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

方法一:排列组合

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

代码比较简单就不展示了,时间复杂度也很明显,就是O(2^N)。

对问题的详细分析:

动态规划有三个核心元素:最优子结构、边界、状态转移方程式。

问题的最优子结构:一个是4金矿10工人时的最优选择,一个是4金矿(10-3)工人时的最优选择。

我们来分析一下最优子结构和最终问题的关系。换句话说,4个金矿的最优选择和5个金矿的最优选择之间,是什么样的关系?

5个金矿的最优选择,就是(前4座金矿10工人的挖金数量)和(前4座金矿7工人的挖金数量+第5座金矿的挖金数量)的最大值。

下面,我们把金矿数量设为N,工人数设为W,金矿的黄金量设为数组G[],金矿的用工量设为数组P[]。

那么5座金矿和4座金矿的最优选择之间存在这样的关系:F(5,10) = MAX( F(4,10), F(4,10-P[4])+G[4] ) 注意:数组下标从0开始。

最后我们还需要确定一下,这个问题的边界是什么?

边界两种情况:(1)只有1座金矿,也就是N=1的时候。这时候没得选,只能挖这座唯一的金矿,得到的黄金数量就是G[0]。(2)如果给定的工人数量不够挖取第1座金矿,也就是 W < P[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]);

方法二:简单递归

把状态转移方程式翻译成递归程序,递归的结束的条件就是方程式当中的边界。因为每个状态有两个最优子结构,所以递归的执行流程类似于一颗高度为N的二叉树。

public class MostGold3 {public static void main(String[] args) {// TODO Auto-generated method stubint[] g = {400, 500, 200, 300, 350};int[] p = {5, 5, 3, 4, 3};System.out.println(getMostGold(5, 10, g, p));}/*** * @param n * @param w* @param g 数组g中存储每座金矿的金矿数* @param p 数组p中存储挖每座金矿需要的工人数* @return 该方法返回工人数为w时挖数组g中前n座金矿所能得到的最大金矿数*///递归解法public static int getMostGold(int n, int w, int[] g, int[] p) { if (n > g.length) throw new RuntimeException("输入的n值大于给定的金矿数");if (n <= 1 && w < p[0]) return 0;if (n == 1 && w >= p[0])return g[0];if (n > 1 && w < p[n-1]) return getMostGold(n-1, w, g, p);int a = getMostGold(n-1, w, g, p);int b = getMostGold(n-1, w - p[n-1], g, p) + g[n-1];return Math.max(a, b);}}

方法的时间复杂度是O(2^N)。

方法三:记忆化搜索方法(备忘录算法)

在简单递归的基础上增加一个HashMap备忘录,用来存储中间结果。HashMap的Key是一个包含金矿数N和工人数W的对象,Value是最优选择获得的黄金数。

方法的时间复杂度和空间复杂度相同,都等同于备忘录中不同Key的数量。

import java.util.HashMap;public class MostGold2 {public static void main(String[] args) {// TODO Auto-generated method stubint[] g = { 400, 500, 200, 300, 350 };int[] p = { 5, 5, 3, 4, 3 };System.out.println(getMostGold(5, 10, new HashMap<MostGold2.Input, Integer>(), g, p));}/*** @describe 该内部类对象用于备忘录算法中作为HashMap存储的键*/private static class Input {private int n;private int w;public Input(int n, int w) {super();this.n = n;this.w = w;}@Overridepublic int hashCode() {final int prime = 31;int result = 1;result = prime * result + n;result = prime * result + w;return result;}@Overridepublic boolean equals(Object obj) {if (this == obj)return true;if (obj == null)return false;if (getClass() != obj.getClass())return false;Input other = (Input) obj;if (n != other.n)return false;if (w != other.w)return false;return true;}}public static int getMostGold(int n, int w, HashMap<Input, Integer> map, int[] g, int[] p) {if (n > g.length)throw new RuntimeException("输入的n值大于给定的金矿数");if (n <= 1 && w < p[0])return 0;if (n == 1 && w >= p[0])return g[0];if (n > 1 && w < p[n - 1]) {Input input = new Input(n - 1, w);if (map.containsKey(input))return map.get(input);int value = getMostGold(n - 1, w, map, g, p);map.put(input, value);return value;}Input input1 = new Input(n - 1, w);Input input2 = new Input(n - 1, w - p[n - 1]);int a = 0; // 用于记录F(n-1,w)的值int b = 0; // 用于记录F(n-1,w-p[n-1])+g[n-1])的值if (map.containsKey(input1))a = map.get(input1);a = getMostGold(n - 1, w, map, g, p);map.put(input1, a);if (map.containsKey(input2))b = map.get(input2) + g[n - 1];b = getMostGold(n - 1, w - p[n - 1], map, g, p);map.put(input2, b);b += g[n - 1];return a > b ? a : b;}}

方法四:动态规划方法

至于动态规划方法的实现,这个问题的参数有两个,也就是存在两个输入维度,怎么能实现自底向上的递推呢?

我们先画出如下表格,表格的第一列代表给定前1-5座金矿的情况,也就是N的取值。表格的第一行代表给定的工人数,也就是W的取值。表格中其余的空白格,代表给定N和W值对应的黄金获得数,也就是F(N,W)。下面让我们来逐行填写表格中的空白。

1. 1金矿时,是400金,5工人。所以前4个格子都是0,因为人数不够。后面格子都是400,因为只有这一座金矿可挖。

2. 第2座金矿有500金,5工人。第二行前4个格子是W < 5, 所以F(N,W)= F(N - 1,W)= 0。

第2行后6个格子计算,因为W >=5,所以根据F(N,W)= MAX(F(N-1,W),F(N-1,W-5)+500),第5-9个格子的值是500。

需要注意的是第2行第10个格子,也就是N=2,W=10的时候,F(N-1,W)=400,F(N-1,W-5)=400,MAX(400,400+500) = 900。

3. 第3座金矿有200金,需要3工人。第3行计算方法和前面一样。

4.  第4座金矿有300金,需要4工人,计算方法同上。

5. 第5座金矿有350金,需要3工人,计算方法同上。

寻找规律: 

我们来思考一下,对于上面每个格子的推导寻找它们的规律。

除了第1行外,每个格子都是前一行的一个或两个格子推导而来的。

比如说3金矿8工人的结果,就是来自于2金矿5工人和2金矿8工人,MAX(500,500+200)=700。

再比如,5金矿10工人的结果,就来自于4金矿7工人和4金矿10工人,MAX(900,500+350) = 900。

在实现程序的时候,我们也可以像这样从左至右,从上到下一格一格推导出最终结果。而且我们并不需要存储整个表格,只需要存储前面一行的结果,就可以推导出新的一行。我们来实现一下代码。

public class MostGold {public static void main(String[] args) {// TODO Auto-generated method stubint[] g = {400, 500, 200, 300, 350};int[] p = {5, 5, 3, 4, 3};System.out.println(getMostGold(5, 10, g, p));}/*** * @param n 第几个金矿* @param w 总共有几个人* @param g 数组,存放每个金矿的黄金数* @param p 数组,存放每个金矿需要的工人数* @return*/public static int getMostGold(int n, int w, int[] g, int[] p) {int col = w + 1;   //因为F(x,0)也要用到,所以表格应该有w+1列int[] preResults = new int[col]; //存放上一行的结果int[] results = new int[col]; //存放当前行的结果//填充边界格子的值for(int i=0; i<=w; i++) {if(i < p[0]) {preResults[i] = 0;}else {preResults[i] = g[0];}}//填充其余格子的值,从上一行推出下一行,外层循环是金矿数量,内层循环是工人数for(int i=0; i<n; i++) {for(int j=0; j<col; j++) {if(j < p[i]) {results[j] = preResults[j];}else {results[j] = Math.max(preResults[j], preResults[j-p[i]] + g[i]);}}for(int j=0; j<col; j++) {preResults[j] = results[j];}}return results[w];}}

最后总结一下DP算法的思路:

核心: 最优子结构、边界条件、状态转移方程 

解题步骤:

1. 建立数学模型

2. 写代码求解问题

   如何建模?

先写出所求问题的最优子结构,进而分析出边界和状态转移方程,数学模型即这2者的组合

对于2输入维度动态规划,画出表格帮助分析,行列分别代表1个输入维度。

如何求解?

建好模后,根据方程组写出自低向上的动态规划代码,一维输入就是1个for循环,二维输入就是2个for循环,如果方程组比较抽象,可以画表格帮助分析。

整理来源:

【1】漫画:什么是动态规划? - 小灰的文章 - 知乎 https://zhuanlan.zhihu.com/p/31628866

【2】https://www.cnblogs.com/hczd123/p/7412950.html

动态规划学习-【国王和金矿】相关推荐

  1. 动态规划:国王与金矿

    题目解析 有一个国家发现了5座金矿,每座金矿的黄金储量不同,需要参与挖掘的工人数也不同.参与挖矿工人的总数是10人.每座金矿要么全挖,要么不挖,不能派出一半人挖取一半金矿.要求用程序求解出,要想得到尽 ...

  2. 动态规划入门之国王的金矿

    最近学习算法,对动态规划不太了解,使用的时候照搬转移方程式,知其然不知其所以然,今天看到一篇动态规划的教程,解释得非常通俗,原文在这里[动态规划入门教程] (http://blog.csdn.net/ ...

  3. 一文弄懂动态规划(DP Dynamic Programming)下楼梯,国王和金矿,背包问题,Dijkstra算法

    动态规划 参考链接 漫画算法,什么是动态规划? DP 动态规划是一种分阶段求解决策问题的数学思想 题目一 问:下楼梯问题,有一座高度是10级台阶的楼梯,从下往上走,每跨一步只能向上1级或者2级台阶,请 ...

  4. 0-1背包问题 题目:国王和金矿问题 描述:有一个国家发现了max_n座金矿,参与挖矿工人的总数是max_people人。每座金矿的黄金储量不同为一维数组gold[],需要参与挖掘的工人数也不同为一维

    题目四:0-1背包问题 题目:国王和金矿问题 描述:有一个国家发现了max_n座金矿,参与挖矿工人的总数是max_people人.每座金矿的黄金储量不同为一维数组gold[],需要参与挖掘的工人数也不 ...

  5. h0156.国王的金矿

    国王的金矿 作者 黄正鹏 单位 贵州工程应用技术学院 国王在他的国家发现了N座金矿,为了描述方便,我们给他们从1到N编号. 对于第i个金矿,需要投入Ci个的费用,能挖出来Wi个单位的金子. 现在国王想 ...

  6. 自适应动态规划学习笔记(3)

    @TOC 自适应动态规划学习笔记(3) 第三天(图全是偷的) 图1 ADP的三个部分 Model Network  书接上回,图(1)中所示的Model Network就是对于系统公式(1)xk+1= ...

  7. 动态规划学习记录:题型/思路汇总

    #动态规划学习记录# 动态规划学习记录:题型/思路汇总 一维数组动态规划 1.爬楼梯 2.数硬币 3.最大子序和 4.区域和检索 - 数组不可变 5.整数拆分 6.打家劫舍 7.打家劫舍II 8.解码 ...

  8. 动态规划经典例题-国王的金矿问题

    金矿问题 问题概述: 有一位国王拥有5座金矿,每座金矿的黄金储量不同, 需要参与挖掘的工人人数也不同.例如有的金矿储量是500kg黄金,需 要5个工人来挖掘:有的金矿储量是200kg黄金,需要3个工人 ...

  9. java经典问题国王_动态规划-国王的金矿问题java

    紧接着上一篇动态规划问题,现在我们开始探讨一个新的问题,问:有一个发现了5个金矿,每一个金矿的储量不同,需要参与挖掘的工人数也不通,参与挖矿工人的总数量是10人,每一座金矿要么全挖,要么不挖,不能派一 ...

最新文章

  1. PreTranslateMessage作用和用法
  2. 考前自学系列·计算机组成原理·存储器画图
  3. “三通一达”创始人均来自桐庐 有的村人均GDP上亿
  4. c语言 #define dpath .exe是什么意思,C语言宏定义#define
  5. nginx重新安装 引起的问题
  6. 使用Managed DirectX编写游戏
  7. mysql最大连接数查询_MYSQL 查看最大连接数和修改最大连接数
  8. 华为荣耀9x怎么解账户锁_麒麟820,4000万像素,荣耀X10是下一部千元街机?
  9. CentOS 6.4下操作kdump执行过程
  10. 堆垛机器人编程技巧_机器人智能堆垛的控制方法与流程
  11. 使用QTP录制自带Flight小实例
  12. 并发编程学习之线程8锁
  13. DB9,DB25,USB-A,USB-B,USB-mini,USB-typeC 接口定义
  14. app测试和接口测试区别
  15. python平方根计算_Python计算平方根
  16. VM无法获取 vmci 驱动程序版本句柄无效解决办法
  17. 数据挖掘背景知识2——数据挖掘可以做到什么 带给我们什么?
  18. 突发,国内技术问答社区SegmentFault竟然被ONES收购
  19. DB-Lib error message 20002, severity 9:\nAdaptive Server connection failed (xxx.xxx.com)\n 报错解决
  20. 致远协同软件个性化功能之标签页版本隐藏

热门文章

  1. GridDehazeNet
  2. 迅捷CAD编辑器如何将图片转换为CAD
  3. 首度公开!我国网络视听用户破9亿,透露出什么信号?
  4. 推荐《悲惨世界》[BD-RMVB.720p.中英双字][2012年最新大片]
  5. JAVA集合框架工具类自定义Collections集合方法
  6. vs2019从远程git仓库拉取代码到本地目录,并切换分支
  7. 免安装绿色软件共享 傻瓜gif制作 flash CS3 动画 影片制作
  8. golang 模拟M/M/s排队系统损失率,泊松分布到达,指数分布服务
  9. 为什么在电力电子装置中,一般采集电压电流用来控制的采用霍尔传感器而不用互感器?
  10. 推荐一个js常用工具函数库etools