换零钱
- 时间限制:1秒 空间限制:32768K
题目描述
考虑仅用1分、5分、10分、25分和50分这5种硬币支付某一个给定的金额。例如需要支付11分钱,有一个1分和一个10分、一个1分和一个5分、六个1分和一个5分、十一个1分这4种方式。
请写一个程序,计算一个给定的金额有几种支付方式。
注:假定支付0元有1种方式。
请写一个程序,计算一个给定的金额有几种支付方式。
注:假定支付0元有1种方式。
输入描述:
输入包含多组数据。 每组数据包含一个正整数n(1≤n≤10000),即需要支付的金额。
输出描述:
对应每一组数据,输出一个正整数,表示替换方式的种数。
输入例子:
11 26
输出例子:
4 13
----------------------------------------------------------我是分割线--------------------------------------------------------------------------------------------------
以下纪录解题过程。
(1) 首先最容易想到的就是循环遍历5种硬币的个数,当对 个数*权值 都进行求和 等于n的时候,
例如 n = 11 时,
c50 = 0 c25 = 0 c10 = 0 c5 = 0 c1 = 11
c50 = 0 c25 = 0 c10 = 0 c5 = 1 c1 = 6
c50 = 0 c25 = 0 c10 = 0 c5 = 2 c1 = 1
c50 = 0 c25 = 0 c10 = 1 c5 = 0 c1 = 1
存在这4中支付方式。
c50 = 0 c25 = 0 c10 = 0 c5 = 1 c1 = 6
c50 = 0 c25 = 0 c10 = 0 c5 = 2 c1 = 1
c50 = 0 c25 = 0 c10 = 1 c5 = 0 c1 = 1
代码如下:
通过测试,当n=1000时,结果可以在要求内得到,但是当n=10000时,时间就无法忍受了,还是太慢啊!!!
此函数的复杂度就为O(N*k),其中k为硬币面值个数,在数据加大时有 k << N,因此复杂度可以写成 O(N)
double smallChange(int N)
{
int c1, c5, c10, c25, c50;
double cnt = 0;
for (c50 = 0; c50 <= N/50; c50++)
{
for (c25 = 0; c25 <= N/25; c25++)
{
for (c10 = 0; c10 <= N/10; c10++)
{
for (c5 = 0; c5 <= N/5; c5++)
{
for (c1 = 0; c1 <= N; c1++)
{
if (c50*50+c25*25+c10*10+c5*5+c1*1 == N)
{
// printf("c50 = %d c25 = %d c10 = %d c5 = %d c1 = %d\n", c50, c25, c10, c5, c1);
cnt++;
}
}
}
}
}
}
return cnt;
}
double smallChange(int N)
{
int c1, c5, c10, c25, c50;
double cnt = 0;
for (c50 = 0; c50 <= N/50; c50++)
{
for (c25 = 0; c25 <= N/25; c25++)
{
for (c10 = 0; c10 <= N/10; c10++)
{
for (c5 = 0; c5 <= N/5; c5++)
{
for (c1 = 0; c1 <= N; c1++)
{
if (c50*50+c25*25+c10*10+c5*5+c1*1 == N)
{
// printf("c50 = %d c25 = %d c10 = %d c5 = %d c1 = %d\n", c50, c25, c10, c5, c1);
cnt++;
}
}
}
}
}
}
return cnt;
}
很明显这种方式太暴力了,时间复杂度为O(N5),n=1000时,就已经慢的难以接受了。。。
然后肯定要想办法降低这个复杂度的
(2) 观察了一下,暴力求解的五个循环,可以看到最内层的循环c1,每次只有一个值是符合条件的,即当c50,c25, c10, c5的值确定了,c1是可以通过公式简单计算出来的,即 c1 = N - c50*50 - c25*25 - c10*10 - c5*5,也就说,实际上我们要的只是统计方式数目,不需要知道每个方式中各种硬币的个数,因此只要判断c50*50+c25*25+c10*10+c5*5 <= N 就可以了
代码如下:
double smallChange_v1(int N)
{
int c1, c5, c10, c25, c50;
double cnt = 0;
for (c50 = 0; c50 <= N/50; c50++)
{
for (c25 = 0; c25 <= N/25; c25++)
{
for (c10 = 0; c10 <= N/10; c10++)
{
for (c5 = 0; c5 <= N/5; c5++)
{
if (c50*50+c25*25+c10*10+c5*5 <= N)
{
// printf("c50 = %d c25 = %d c10 = %d c5 = %d\n", c50, c25, c10, c5);
cnt++;
}
}
}
}
}
return cnt;
}
通过测试,当n=1000时,结果可以在要求内得到,但是当n=10000时,时间就无法忍受了,还是太慢啊!!!
(3)继续观察上面的函数,最内层的循环c5,我们可以看到,当c50,c25, c10 的值确定的时候,c5和c1的支付方式是可以通过公式确定的,即 cnt += (temp/5+1);
代码如下:
double smallChange_v2(int N)
{
int c1, c5, c10, c25, c50;
double cnt = 0;
int temp = 0;
for (c50 = 0; c50 <= N/50; c50++)
{
for (c25 = 0; c25 <= N/25; c25++)
{
for (c10 =0; c10 <= N/10; c10++)
{
temp = N - c50*50 - c25*25 - c10*10;
if (temp >= 0)
{
cnt += (temp/5+1);
}else
{
break;
}
}
}
}
return cnt;
}
到这里,已经将原来的复杂度O(N5)改为现在的O(N3),n=10000的时候能够很快的得到答案(至少在我的机子上,根本感觉不到超过了1秒啊!!!),悲剧的是,还没有通过OJ啊!!!/(ㄒoㄒ)/~~
今天想了好长时间都不知道怎么改,也怪自己对算法没什么太深入的研究,完全没有想到用动态规划来做。。。/(ㄒoㄒ)/~~ (动态规划的百度百科:http://baike.baidu.com/link?url=RcauIFdRKEbjsBMx3ifhTNGy6Fe_s3aHspW0h3uCxv6pXRkgUa105ugwWLVC6wy2sccYasxdj_zUPOFBuYOydq)
在此感谢老毛同学,看了他的解法我才明白过来,这个问题其实和上台阶是一样的。。。
吃个饭去,回来再写O(∩_∩)O~
-----------------------------------------------------------------------继续刚才的---------------------------------------------------------------------------------------------------------------------
(4)根据动态规划的思想,我们生成一个数组brr保存支付方式种类数,长度设定为10001,。首先计算只有1分硬币的情况,此时无论n是多少,支付方式都只有一种并记录在数组brr中;然后,只有1分和5分硬币的情况,此时从5分开始计算(小于5分就不能用5分表示),于是brr[ j ] = brr[ j ]+brr[ j-5 ] ,等号左边brr[ j ] 表示 j 分钱的支付方式种类数,等号 右边 brr[ j ] 表示只有1分硬币的时候的支付方式种类数而brr[ j-5] 表示,在j-5分钱的支付方式种类数已知的情况下,加上5分钱就可以等于j 分钱了。当硬币面值增大增多时,也是如此推导。
说的简单点就是可以用 arr[] = {1, 5, 10, 25, 50}来表示硬币的面值。 brr[10001]来存储总共的支付方式种类。
而 j 分钱的支付方式种类数就是等于,在brr[ j - arr[ i ] ] (i = 0~ 4) 的种类数,加上arr[ i ] 面值的硬币或者不加arr[ i ] 面值的硬币这两种情况,
即有递推公式 brr[ j ] = brr[ j ]+brr[ j - arr[ i ] ]
代码实现如下:
double smallChange_v4(int N)
{
int arr[5] = {1, 5, 10, 25, 50};
double brr[10001] = {0};
brr[0] = 1;
for (int i = 0; i < 5; i++)
{
for (int j = arr[i]; j <= N+1; j++)
{
brr[j] += brr[j - arr[i]];
}
}
return brr[N];
}
此函数的复杂度就为O(N*k),其中k为硬币面值个数,在数据加大时有 k << N,因此复杂度可以写成 O(N)
综上,此题就算完美的解决了吧。。。。O(∩_∩)O~~