凑硬币(58同城2017校招笔试题)

暴力破解,循环递归实现,代码如下:

/** * 暴力破解,循环递归,找出了所有可能的组合并进行了存储,* 在循环递归的时候,因为选取的分类相互是有重叠的,生成的递归树分支出现重复,而递归函数最终返回的就是总的拼凑数目,最终会导致总的数目重复,不得不进行判断,效率低下。。*/
package others;import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;public class CoinCoin58 {//测试用例public static void main(String[] args) {int m = 20;int[] a = {1, 5, 10, 20, 50, 100};ArrayList<ArrayList<String>> arr = coinCoin58(m, a);System.out.println(arr);System.out.println("总数 = " + arr.size());}//找出最少的钱的数目private static ArrayList<ArrayList<String>> coinCoin58(int m, int[] a) {ArrayList<ArrayList<String>> arr = new ArrayList<ArrayList<String>>();HashMap<String, Integer> h = new HashMap<String, Integer>();coinCoin58(m, a, arr, h);return arr;}private static void coinCoin58(int m, int[] a, ArrayList<ArrayList<String>> arr, HashMap<String, Integer> h) {//递归结束条件if (m == 0) {ArrayList<String> arr_temp = new ArrayList<String>();// 将HashMap中的键值对全部取出,存储到arr中。for (Iterator<String> it = h.keySet().iterator(); it.hasNext();) {String key = (String) it.next();for (int j = 0; j <= h.get(key); j++) {// 键所对应的值位2,就增加两个键,否则增加一个键arr_temp.add(key);}}if (!arr.contains(arr_temp)) {arr.add(arr_temp);}}for(int i = 0; i < a.length; i++) {if(m - a[i] >= 0) {if(!h.containsKey(Integer.toString(a[i]))) {h.put(Integer.toString(a[i]), 0);coinCoin58(m-a[i], a, arr, h);h.remove(Integer.toString(a[i]));} else {h.put(Integer.toString(a[i]), h.get(Integer.toString(a[i]))+1);coinCoin58(m-a[i], a, arr, h);h.put(Integer.toString(a[i]), h.get(Integer.toString(a[i]))-1);}}}}
}

上面是比较常规的思路,但是并不实用,下面采用动态规划,自顶向下解决,注释已经写在代码里面了。

//############################  动态规划,自顶向下  ####################################
/** 循环递归来实现,注意这里分类是按照是否使用某个币值来划分的,这样划分不会在递归树中出现重复分支* 举个例子来说:m = 10,一共有四中凑硬币的方案。* {10, 1}* {5, 5, 1}* {5, 1, 1, 1, 1, 1, 1}* {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}* 第一步,是否使用了币值为100的硬币,显然都没有使用 ;* * 第二步,是否使用了币值为50的硬币,显然都没有使用;* * 第三步,是否使用了币值为20的硬币,显然都没有不使用;* * 第四步,是否使用了币值为10的硬币,这时就将集合划分成了两个子集:*          使用了10:  {10, 1}*          没使用10    {5, 5, 1}、{5, 1, 1, 1, 1, 1, 1}、{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}* 第五步,针对使用了10的情况:此时应该让m = m -10,然后继续对m进行是否使用了币值为10的硬币。*      针对没使用10的情况,继续判断是否使用了币值为5的硬币** 第六步,针对第五步出现的可能的多种结果,继续进行判断* 。。。。。。* 。。。。。。* 重复上述结果,结果集合中的4个元素会落入各自的类别中。* * 注意一点,在计算拼凑m可能的组合方案时,会出现重复计算,可以通过维护一个HashMap来提高效率。* 也出现了大量的重复情况,可以通过维护一个HashMap来避免重复,提高效率,但是牺牲了空间复杂度。*/
import java.util.HashMap;public class CoinCoin58 {// 测试用例public static void main(String[] args) {int m = 1000;int[] a = { 1, 5, 10, 20, 50, 100 };//h用于优化代码,牺牲空间复杂度,换取时间复杂度HashMap<String, Integer> h = new HashMap<String, Integer>();int result = coinCoin58(m, a, a.length - 1, h);System.out.println("总数= " + result);}private static int coinCoin58(int m, int[] a, int length, HashMap<String, Integer> h) {if(m == 0){return 1;}if(m < 0) {return 0;}if(length < 0) {return 0;}
//      /*
//       * 方法一:
//       * 优化,拿空间复杂度换取时间复杂度
//       */
//      int temp = 0;
//      if(!h.containsKey(Integer.toString(m) + "&" + Integer.toString(length))) {//          temp = coinCoin58(m, a, length - 1, h) + coinCoin58(m-a[length], a, length, h);
//          h.put(Integer.toString(m) + "&" + Integer.toString(length), temp);
//          return temp;
//      } else {//          return h.get(Integer.toString(m) + "&" + Integer.toString(length));
//      }/** 方法二* 不优化*/return coinCoin58(m, a, length - 1, h) + coinCoin58(m-a[length], a, length, h);}
}

现在,我们换个思路,既然自顶向下可以,那么逆转思维,自底向上自然也可以解决。

//############################## 动态规划  自底向上  ###################
public class CoinCoin58 {
// 测试用例public static void main(String[] args) {int m = 1000;int[] a = { 1, 5, 10, 20, 50, 100 };int result = coinCoin58(m, a);System.out.println("总数= " + result);}private static int coinCoin58(int m, int[] a) {int[][] table = new int[m + 1][a.length];  // table[i][j]表示钱数为i、且使用币值为a[0]、a[1]....a[j]的硬币来拼凑的总方案。for (int i = 0; i < a.length; i++) {table[0][i] = 1;            } for (int j = 1; j < m + 1; j++) {for (int k = 0; k < a.length; k++) {// 包括 a[k] 的方案数int x = (j - a[k] >= 0) ? table[j - a[k]][k] : 0;// 不包括 a[k] 的方案数int y = (k >= 1) ? table[j][k - 1] : 0;table[j][k] = x + y;}}return table[m][a.length-1]; }
}

到这里时间和空间复杂度,已经比较小了,但是还有神一样的解法。

//#########################  神一样的解法   ########################
/** 这两个for循环用的十分巧妙,其循环过程描述* 当只有0元硬币可以使用时,拼凑n元的方案数显然等于0。* * 当只有0、1元硬币可以使用时,拼凑n元的方案数 = * 当只有0元硬币可以使用时,拼凑n元的方案数 + * 当只有0、1元硬币可以使用时,拼凑n-1元的方案数 +* * 当只有0、1、5元硬币可以使用时,拼凑n元的方案数 =* 当只有0、1元硬币可以使用时,拼凑n元的方案数  +* 当只有0、1、5元硬币可以使用时,拼凑n-5元的方案数* * 当只有0、1、5、10元硬币可以使用时,拼凑n元的方案数 =* 当只有0、1、5元硬币可以使用时,拼凑n元的方案数  +* 当只有0、1、5、10元硬币可以使用时,拼凑n-10元的方案数* * 。。。。。。。* * 如何理解上述过程呢?* 还是一个集合分类的思想,比如我们的钱的数目是17,那么可使用的币值为1,5,10* 我们把结果集合按照是否使用了10分成了两类:第一类是使用了10币值的方案,  第二类是只使用了1,5币值的方案* 第二类只使用了1,5币值的方案,在上一次循环中已经求出,那么第一类使用了10币值的方案如何求解呢?* 这里我们用17-10等于7,然后使用币值为1,5,10拼凑7的方案已知。* 减10就保证了第一类方案中一定会出现10,从而不与第二种方案重复* */
public class CoinCoin58 {// 测试用例public static void main(String[] args) {int m = 1000;int[] a = { 1, 5, 10, 20, 50, 100 };int result = coinCoin58(m, a);System.out.println("总数= " + result);}private static int coinCoin58(int m, int[] a) {int[] table = new int[m + 1];table[0] = 1;for (int i = 1; i < m + 1; i++) {  //赋初值table[i] = 0;}for (int i = 0; i < a.length; i++) {for (int j = a[i]; j <= m; j++) {table[j] += table[j - a[i]];}}return table[m];}
}

* 到这里,就彻底结束了,一共四种方法,其中动态规划的两种方法,在思想上是相同的,都是把一个大问题进行了划分。但是这个划分是关键点,因为我们的第一种方法暴力破解,它也是把问题进行了划分,先取一个1或5或10或20或50或100这一个硬币,然后再拼凑剩下的硬币,但是这个为什么就复杂了呢?因为我们在对问题进行划分的时候,实际上是把问题的解答集合划分成子集合,而上面那种方式划分的子集合中出现了重复的元素。
 比如,拼凑16,问题的解答集合:
{10,5,1}
{5,5,5,1}
{5,5,1,1,1,1,1,1,1,1,1,1}
{10,1,1,1,1,1,}
{5,1,1,1,1,1,1,1,1,1,1}
{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,}
一共是6中可能,注意集合里面元素的排列顺序是无关紧要的,只看元素的种类和个数是否一样。而在第一种方法暴力破解中,按先选取1,5。。。,再拼凑剩下的硬币的方案,显然不能把上述集合分离开,比如,上述{10,5,1}按照第一份方案的分解方式,就会以
 {1,5,10}和{1,10,5}的形式出现在先取一个币值为1的硬币子集中,
 {5,1,10}和{5,10,1}的形式出现在先取一个币值为5的硬币子集中,
 {10,5,1}和{10,1,5}的形式出现在先取一个币值为10的硬币子集中,
显然,上述6种形式是同一种拼凑方案!这样就让递归树的分支出现了重复,而题目求解的就是总数目,导致结果出现错误。
而按照是否使用某个币值来划分,就能把集合完美的分成两个子集合,然后再进一步分解,例如按照是否使用币值5来划分:
 第一类:{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,} {10,1,1,1,1,1,}
 第二类:{10,5,1} {5,5,5,1} {5,5,1,1,1,1,1,1,1,1,1,1} {5,1,1,1,1,1,1,1,1,1,1}
显然,上述两个集合中无重复!

综合上述分析过程会发现,在运用动态规划时,集合的划分
应该首先遵循一个无重复元素的原则,尤其是重复划分会影响最终结果的正确性的时候;其次,也可以采用重复划分,当出现重复不会影响结果的正确性,只会降低效率的时候,这时可以通过后期优化来降低重复。切记!
这样写出的代码才是真正属于动态规划的代码!

参考资料:http://blog.csdn.net/jiyanfeng1/article/details/40559111

凑硬币(58同城2017校招笔试题)相关推荐

  1. 58同城秋招笔试题解答 --map模式按value进行排序

    求字符串中字母重叠的次数并按要求输出(笔试题) 题目描述: 给定一个字符串,求出字母重叠出现的次数,最后按字典格式输出. 输入示例: aaabccbbbfffaddbaa 输出示例: a : 5 b ...

  2. 微软2017校招笔试题3 registration day

    题目 It's H University's Registration Day for new students. There are M offices in H University, numbe ...

  3. 今日头条2017校招笔试题

    一些出题人出了n道题,每道题有一个难度系数,难度系数满足以下关系的3道题可以组成一套试卷,为了使这n道题使用上且只能使用一次,问出题人最少还要出多少题? a<=b<=c b-a<=1 ...

  4. 微软2017校招笔试题2 composition

    题目 Alice writes an English composition with a length of N characters. However, her teacher requires ...

  5. 互联网公司招聘--58集团--前端--2017年笔试题1

    互联网公司招聘–58集团–前端–2017年笔试题1 互联网公司招聘–58集团–前端–2017年笔试题1 互联网公司招聘–58集团–前端–2017年笔试题1

  6. 58集团2017校招(第一次正规的笔试)

    准备出去实习了,以前都是喜欢写书面笔记的,现在差不多有3本密密麻麻的笔记了,这也是传承了我的导师<每日一篇书面博客>的老传统,现在想想看着自己在这点上的每天坚持,更多的是感谢导师的用心良苦 ...

  7. 关于python类的继承正确的说法是_2017美团点评的运维岗校招笔试题,测测你会几题?...

    原标题:2017美团点评的运维岗校招笔试题,测测你会几题? 1.数据库:以下哪项不是HASH索引的特征? A MySQL不能确定在两个值之间大约有多少行 B 不能使用hash索引来加速ORDER BY ...

  8. 58同城2020校招转转算法岗笔试编程题

    58同城2020校招转转算法岗笔试编程题 第一题 题目描述:年末了要评选优秀员工了,给定一个优秀员工比率,在员工的评分上计算出能被选中的优秀员工的评分阈值.优秀员工人数=员工人数*优秀员工比率.员工人 ...

  9. 数据分析真题日刷 | 欢聚时代2018校招笔试题-产品经理/数据分析/游戏运营/市场专员 A卷

    今日真题 欢聚时代2018校招笔试题-产品经理/数据分析/游戏运营/市场专员 A卷(来源:牛客网) 题型 客观题:单选5道,不定项选择10道 主观题:问答4道 完成时间 90分钟 牛客网评估难度系数 ...

最新文章

  1. DevExpress z
  2. 正则表达式--元字符和限定词
  3. uniapp打包成html5包个ios壳,HBuilder之uni-app打包App方法
  4. 2016-2017-2 《Java 程序设计》课堂实践项目
  5. [游戏杂谈]浅谈游戏打击感
  6. ios第3天的气泡作业
  7. 如何给PDF文件添加水印?
  8. STM32 Mbed系列-ADC参考电压设置
  9. 计算机和信息技术革命,人类历史上的四次信息技术革命
  10. 安Linux基础入门教程
  11. Discord教程:Discord账号注册、Discord多账号登录和管理
  12. 微信刷卡 sdk java_微信支付 Java SDK
  13. 回归算法(最小二乘法拟合)
  14. 苍蝇眼睛_桌子的布局像苍蝇一样下降=)
  15. 应用数据库软件的典型架构有哪些
  16. 基本初等函数 对数函数
  17. django1.11 mysql配置_使用Django1.11创建简单的资产管理平台
  18. OpenERP采购的两种不同情况
  19. mybatis 如何切割字符串 查询多个值
  20. 很值得一读精彩语句 转自@红薯

热门文章

  1. [PyQt] Python界面编程学习总结
  2. 记一次艰难的重装系统
  3. 基于云开发的微信小程序-miNi相册(主页与上传图片功能)
  4. 【算法题】机器人走迷宫
  5. 单元测试的艺术--读书笔记
  6. 《计算机网络》day03-计算机网络的体系结构
  7. Latch及latch冲突
  8. Java中文编程开发,让Java编写更加容易
  9. 微信小程序background-image设置(文末还有wxss的万能模板)
  10. ASP.NET 用 FlexPaper 在页面上显示 PDF 文件