换零钱问题--某条笔试真题01

首次感受互联网公司的氛围,没有想象中的高大上,不过感觉leader应该是个nice的人,分享几句印象颇深的话,行业决定你的下限,公司决定你的上限(选择很重要);和优秀的人做有挑战的事;你永远也不可能准备好;你的成长速度和你解决的问题的难度正相关。
来还昨天的flag,今天就写第一道题吧。下面仅给出自己的答案,若有错误,还请批评指正。

一、兑换零钱
题目:现在有2元,3元,5元三种面值的硬币。给定数组arr,arr的长度等于3,每个元素分别代表各种硬币的数量,再给定一个整数aim代表要找的钱数,求组成aim的最小货币数。

输入样例:
aim = 20; arr = [3,4,5]
输出样例:
4

public class MinCoins_01 {
	public static int minCoins(int target, int[] arr) {
		if (arr == null) {
			return 0;
		}
		int min = Integer.MAX_VALUE;
		Boolean flag = false;
		for (int i = 0; i != arr[0]; i++) {
			int value = target - 2 * i;
			if (value < 0) {
				continue;
			}
			for (int j = 0; j != arr[1]; j++) {
				value = target - (2 * i + 3 * j);
				if (value < 0) {
					continue;
				}
				for (int k = 0; k != arr[2]; k++) {
					value = target - (2 * i + 3 * j + 5 * k);
					if (value < 0) {
						continue;
					}
					if (0 == value) {
						min = Math.min(min, i + j + k);
						flag = true;
					}
				}
			}
		}
		return flag ? min : -1;
	}
	public static void main(String[] args) {
		int aim = 18;
		int[] coins = { 2, 3, 5 };
		int[] coinNumber = { 5, 5, 5 };
		int result = minCoins(aim, coinNumber);
		System.out.println(result);
		System.out.println("Hello,Markov_Jin");
	}
}

思路:因为限定了每个硬币的使用数量,且只有3中面值的货币,可以暴力将这3个数排列的所有情况,选出满足和符合aim的组合,并比较出数量最小的组合。但是这样做显然复杂度较高,虽然每一次遍历都提前进行了剪枝,但仍然不是很好的解答。
看到这道题的时候我第一反应就是动态规划,左神的书上看到过,然而怎么也想不起来,最后只能老老实实暴力。下面我想分享一下动态规划的解法。原题中给定了每种硬币的数量,一定程度上减低了问题的难度,这样我们会想到遍历所有可能的数量,但是如果题目改为,每种面值的货币可以使用任意枚,那样就不能再指望我们的暴力出奇迹了。

用一个例子来讲解动态规划解法:
输入样例:
aim = 10; arr = [2,3,5]
输出样例:
4

思路:动态规划需要确定三要素:数组的行和列,数组中每个元素代表的含义,寻找数组前后之间的关系,利用问题的答案(一般在dp[n][n] 或者dp[0][0])
大半夜突然脑洞大开,想谈谈我对动态规划的理解,简单的说就是记住你走过得每一个角落,当你想去下一个未曾去过的地方的时候,你就可以在你的记忆力找到你曾到过的距离未知地方最近的角落,举个脑洞更大的例子,比如你是LOL的盲僧,你曾在河道里打死过河蟹并随手在河道插了针眼,当你下次想去偷Buff的时候不知道哪条路最近的时候,你就可以直接传送到河道,去拿buff。
首先生成一个aim*arr.length(10x3)的二维数组dp[arr.length][aim]。
dp[i][j]:表示在可以任意使用arr[0…i]货币的情况下,组成j所需要的最小硬币数。本题中dp[1][2]代表着可以使用硬币{2,3}的情况下,组成aim=2所需要的最小硬币。dp[2][10] (使用硬币{2,3,5}组成aim=10的最小数组)就是我们要求解的答案,我们通过分析数组元素之间的关系利用左上方的dp[i’][j’]求解当前的dp[i][j]。

  • 根据定义,先初始化数组中已知的部分元素。数组第一行dp[0][0…aim]表示只能使用arr[0]==2货币的情况下,找某个钱数需要的最少个数。此时只能找到的钱数是0元、2元、4元。。。10元。对应的最少货币数是0、1、2。。。5个,数组中对应的值dp[0][0]==0,dp[0][2]=1,。。。dp[0][5]=10。第一行的其他位置所代表的钱数则一律找不开,统一设为32位整数的最大数,如下图所示。
  • 从第二行起,从左到右、从上到下计算。假设计算到位置(i,j),dp[i][j]的可能值来自下面的情况。
1. 完全不使用当前arr[i],只使用arr[i-1]的最少货币数,即dp[i-1][j]的值。
2. 只用一张当前arr[i]的最少货币数,即dp[i-1][j-arr[i]]+1的值
3. 只用2张当前arr[i]的最少货币数,即dp[i-1][j-2*arr[i]]+2的值
4. 只用3张当前arr[i]的最少货币数,即dp[i-1][j-3*arr[i]]+3的值
...
上述所有的情况中,取值使用张数最小的情况。

用公式表示上面的所有情况:

d p [ i ] [ j ] = m i n { d p [ i 1 ] [ j a r r [ i ] k ] + k ( k 0 } ; dp[i][j]=min\{dp[i-1][j-arr[i]*k]+k (k \geqslant 0\}; dp[i][j]=min{dp[i1][jarr[i]k]+k(k0};

= m i n { d p [ i 1 ] [ i ] + [ j a r r [ i ] ( k 0 ) ] + k 0 ( k 0 } ; =min\{dp[i-1][i]+[j-arr[i]*(k-0)]+k-0 (k \geqslant 0\}; =min{dp[i1][i]+[jarr[i](k0)]+k0(k0};

= &gt; m i n { d p [ i 1 ] [ j ] , d p [ i 1 ] [ j a r r [ i ] x ] + x } ( x 1 } ; =&gt;min\{dp[i-1][j],dp[i-1][j-arr[i]*x]+x\} (x \geqslant 1\}; =>min{dp[i1][j],dp[i1][jarr[i]x]+x}(x1};

令x=y+1
d p [ i ] [ j ] = &gt; m i n { d p [ i 1 ] [ j ] , d p [ i 1 ] [ j a r r [ i ] a r r [ i ] y ] + y + 1 } ( y 0 } ; dp[i][j]=&gt;min\{dp[i-1][j],dp[i-1][j-arr[i]-arr[i]*y]+y+1\} (y \geqslant 0\}; dp[i][j]=>min{dp[i1][j],dp[i1][jarr[i]arr[i]y]+y+1}(y0};

d p [ i ] [ j a r r [ i ] ] = m i n { d p [ i 1 ] [ ( j a r r [ i ] ) a r r [ i ] y ] + y ( y 0 } dp[i][j-arr[i]]=min\{dp[i-1][(j-arr[i])-arr[i]*y]+y(y \geqslant 0\} dp[i][jarr[i]]=min{dp[i1][(jarr[i])arr[i]y]+y(y0}

d p [ i ] [ j ] = &gt; m i n { d p [ i 1 ] [ j ] , d p [ i ] [ j a r r [ i ] ] + 1 } ; dp[i][j]=&gt;min\{dp[i-1][j],dp[i][j-arr[i]]+1\} ; dp[i][j]=>min{dp[i1][j],dp[i][jarr[i]]+1};

dp[i][j]的值由其上方dp[i][j]和其左边的dp[i][j-arr[i]]确定,讨论这两种情况

  • arr[0…i]组成j钱数j,要么不使用arr[i],最小钱数为arr[0…i-1]组成j的钱数dp[i][j-arr[i]],
  • 要么使用arr[i]arr[i],若arr[0…i]组成总钱数j-arr[i]的最小钱数存在(既能找开j-arr[i]),则找开总数j的最小钱数就是比j-arr[i]多使用一张面数为arr[i]的钱,所以找开总数j的最小钱数为dp[i][j-arr[i]]+1。
  • 最后比较这两种情况的大小,选最小值,根据规律直接填满dp数组,最后一个元素dp[2][10]既是本题的解,[2,3,4]组成10的最小硬币数是2枚:即两枚5元的硬币。

public static int minCoins2(int target, int[] arr) {
		if (arr == null || arr.length == 0 || target < 0) {
			return 0;
		}
		int max = Integer.MAX_VALUE;
		int[][] dp = new int[arr.length][target + 1];
		// int value = -1;
		for (int i = 0; i != target + 1; i++) {
			dp[0][i] = max;
			if(j-arr[0]>0]&&dp[0][i-arr[0]!=max){
			dp[o][i]=dp[0][i-arr[0]+1
			}
			//dp[0][i] = i % arr[0] == 0 ? i / arr[0] : max;
		}
		int left = 0;
		for (int i = 1; i != arr.length; i++) {
			for (int j = 0; j != target + 1; j++) {
				left = max;
				if (j - arr[i] >= 0 && dp[i][j - arr[i]] != max) {
					left = dp[i][j - arr[i]] + 1;
				}
				dp[i][j] = Math.min(dp[i - 1][j], left);
			}
		}

		return dp[arr.length - 1][target] != max ? dp[arr.length - 1][target] : -1;
	}

此题的时间复杂度为O(N^2 * aim),空间复杂度为O(N*aim).下次将补充空间复杂度将为O(aim)的算法。

全部评论

相关推荐

面试官人很好,态度和蔼可亲,没答出来时也会引导你去思考。由于是晚上面的,导致我白天一天都有点紧张,面的时候状态也不是很好,正常可能面试官提问完应该思考几秒再答,而我就像抢答一样一口气把所有会的都说出来,这样就导致逻辑比较混乱,东一句西一句的。首先是自我介绍,先把会的技术大致讲一下,由于我八股背的多所以着重讲了一下,Java,go,jvm,MySQL,Redis,计网,操作系统这些,然后一小部分闲聊,然后先问了一下项目,面试官问我这个项目是否落实之类的,直接坦言说是写的练手的,包括之前也写过IM通讯,外卖之类的。然后面试官就把提问的重点放在了八股上。先问了Java:类加载器(答:3种+自定义类加载器、tomcat、原因+双亲委派+好处)JVM参数(答:xmx,xms,newsize这些,问我是如何设定的,我回答是把内存分一半给堆,再把堆分一半给新生代,这方面确实不太了解)然后问了一下并发相关的:线程池(答:线程池的7个参数(忘了线程工厂和阻塞时间了),3个重要参数,还有线程如何启用,为什么要设计最大线程数之类的,提到Java栈默认分配1MB运行时不可以更改)AQS(答:先讲clh是自旋锁+list,然后是AQS在这个基础上做的两个优化,然后举了一下reentrantlock根据state如何获取资源)CAS(答:使用三个字段,aba问题,然后将通常搭配自旋锁实现,面试官问通常会自旋多少次,这个不太了解,答的100,然后问100次大概多少秒,回答微秒级,然后面试官讲了一下怎么做资源可能没用完,意识到可能还需要进行阻塞操作)然后考虑一下Linux命令(top,ps,如何使用管道符过滤线程和使用Linux启动线程没答出来)然后问Redis:持久化机制(答:三种aof,rdb,混合,aof的三个参数刷盘策略,rdb以快照保存,使用bgsave会使用子线程来保存不会阻塞,而aof虽然会阻塞但是只在写完数据后追加一条命令,不会太影响,然后是他俩的优缺点,还有混合是怎么保存数据的)集群模式(答:三种,主从复制到缺点再到哨兵机制,正常使用三个哨兵互相监督,主节点挂了投票选主哨兵然后选主节点,然后额外讲一下脑裂的问题,主节点进行数据更新然后把命令写入aof来同步从节点,最后cluster集群,如何实现,使用16383个哈希槽(艹答成16384了),先根据哈希码取余,再根据节点数取余决定放在哪个节点上,然后问了一下我会怎么选集群模式,首先是cluster的问题,会让管道操作之类的失效,然后哨兵会导致整个集群结构变得复杂,使用小项目可能会考虑哨兵,大的考虑cluster,然后考了一下cluster如果一个节点挂了怎么办,根据节点数重新取余然后数据转移,面试官说这么转移比较慢,有没有别的办法,我隐约记得使用一个类似环形数组的方式,想不起来了)然后考了一下MySQL的b+树(这方面的知识点太多了,导致我什么都想讲逻辑就比较乱,讲了一下聚簇索引,树的叶子节点对应着一张页16KB,MySQL有一个区的概念,把这些页放在同一个区中,这样叶子节点的双向链表遍历时速度更快,然后b+树的扇出比较大(非常二,说成扇度之类的,面试官以为说的是扇区)这样层数就比较小,一行1kb数据的话3层可以放心2000w数据)其他的暂时想不起来了算法是lru,面试官问要不要提示,我说写个,然后写了10分钟左右,说大概写好了,但是面试官指出了2个小错误,第一个马上就改回来了,第二个一直没看出来(大脑这时候已经停止工作了)反问:问学习建议,说根据实际的项目进行深入,考虑应该怎么做,还问了一下组里面是做Java的吗?面试官说他是做go的,组里什么语言都有,语言影响不大,连忙补充了一句我对go的底层有深入源码的学习)结束。总体感觉答得不太好,没有太体现出深度,细节也不够全面。
下一个更好呗:佬,我投完云智一直没消息,多久约的一面啊
查看14道真题和解析
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务