您的位置:首页 > 其它

【笔试题】一个无重复面值的找零算法的思路与实现

2012-09-01 18:01 295 查看
在论坛上看到有人问了一个类似的算法题:

给出升序排列的N个数字,比如1, 2, 3, 7, 70

找出无法被这组数字组成的最小正整数。(这组数字中每个数字最多使用一次)

(1)简单描述你的算法和思路。

(2)用C/C++实现

(3)分析你的代码的时间复杂度和空间复杂度

解题思路:

这个问题类似于一个硬币找零问题的升级版。现存在一堆面值为V1, V2, V3, ...的硬币,每种面值的硬币只有一枚,现在需要为顾客找出总值为sum的零钱。问不能被找零的sum的最小值是多少?

方案1:

算法概述

最容易理解的实现方法是使用递归。

我们假定V1, V2, V3, ... Vn是面值由小到大排列的所有币种。我们从sum等于1开始,递增的循环进行找零。

首先找出小于等于sum且最接近sum的面值Vm。然后得出sum-Vm的值,并对sum-Vm继续进行找零,找出小于等于sum-Vm且小于Vm的最接近面值。递归的找下去,如果连最小面值都被遍历到而sum仍然没被找完,则不能被找零。

若sum不能被无重复面值的找零,则sum即所求。否则,对sum+1进行递归的找零。

实现

如下是我用C语言实现的算法,可以直接运行。可以为values指定任意的一组升序面值。

#include "stdio.h"
#include "conio.h"
static int values[]={1,2,3,12};/*升序排列的所有面值*/
void main()
{

int sum;
/*如果最小的币值都大于1,则1必定不能被找零*/
if(values[0]>1){
printf("1 is the minimum.\n");
}
else{
for(sum=1;; sum++){
int i,j;
/*找出最接近sum且不大于sum的面值在values数组中的下标*/
for(i= sizeof(values)-1; i>=0; i--){
if(values[i]<=sum){
j=i;
break;
}
}
if(recursion(j,sum-values[j])==-1){/*该sum不能被找零*/
printf("%d is the minimum.\n",sum);
break;
};
}
}
getchar();
}
int recursion(int j, int sum){
int i,k;
if(j<1 && sum>0)
return -1;/*不能被找零*/
if(sum==0)
return 0;/*可以被找零*/
/*找出最接近sum且不大于sum,小于values[j]的面值在values数组中的下标*/
for(i= j-1; i>=0; i--){
if(values[i]<=sum){
k=i;
break;
}
}
return recursion(k,sum-values[k]);
}


算法正确性证明

有同学

问我”首先找出小于等于sum且最接近sum的面值Vm“的这种做法是不是正确。会不会存在某些情况,sum的值大于Vm,但是并不能被包含Vm的硬币集合表示,反而可以被一组小于Vm的硬币集合表示呢?例如{ 1, 2, 5, 6, 7 }在判断11能否被找零的过程中会取Vm = 7,而sum - Vm = 11 - 7 = 4,4不能被找零,但实际上11可以由5,
6找零。
然而实际上这种情况是不存在的。因为程序是从sum等于1开始对sum进行递增的判断,如果sum=k时不能被找零,则所求的最小值就是k,程序将不会继续往下进行。也就是说上段所说的情况是不存在的,因为4已经是所求的不能被找零的最小值,程序将跳出循环。

接下来我们来证明算法的正确性。
在sum=1或sum=2的情况下很明显,算法正确。根据数学归纳法,我们只需证明对于面值集G={ V1, V2, ..., Vn },在sum=1, 2, ...,k(k>=1)都可以被正确找零的情况下,算法对sum=k+1的验证是正确的,即可证明算法正确。而要证明这点,我们只需证明递归算法是正确的,即取不大于且最接近sum的Vm的做法是正确的。

首先我们证明一些辅助命题。如果嫌长也可以先看下面正题的证明再来查阅这两个辅助命题。
1.对于面值集G={ V1, V2, ..., Vn },若G可以为1, 2, ...,Vm(m<=n)找零,则由G的前m个连续面值组成的子集A={ V1, V2, ...Vm}必定可以为1, 2, ...,Vm找零。

证明:首先能为sum=x找零的面值均小于等于x。(比如为5找零,零钱中肯定不会有6)。所以能为x找零的面值中可以不包含比x更大的面值。
若G能为x找零,则G中所有不大于x的面值的集合也能为x找零,因为大于x的面值在找零的时候根本不可能被用到。
已知面值集G可以为1, 2, ...Vm找零,因此G的所有不大于Vm的面值的集合也可以为1, 2, ...Vm找零。
得证。

2.对于面值集G={ V1, V2, ..., Vn },1, 2, ... ,k均可以被找零。其中q(1<q<=k)可以被G的前r个连续面值的集合A={ V1, V2, ...Vr}(r<=n)找零。证明1, 2, ...,q-1均可以被A找零。

证明:取A的所有不大于q的面值集合B={ V1, V2, ...Vt},由命题1得,B也可以为q找零。

设q=Va+Vb+...(其中Va, Vb, ...为升序排列且均属于面值集合B)
1)若Va=1,则将为q找零的集合中的Va去掉即可表示q-1
2)若(Va)-1属于B,只需将q的找零集合中的Va替换成Va-1即可表示q-1.
3)若(Va)-1不属于B,由命题1可知B可以为1,2,...Vt找零。由于1<(Va)-1<Vt,所以(Va)-1可以被B找零。由命题1,(Va)-1也可以被B中所有不大于(Va)-1的面值的集合找零,即被C={ V1, V2, ...V(a-1)}找零。
由于q的找零面值组合中最小为Va,所以(Va)-1被C找零形成的面值集组合与q的面值集组合必没有重复。只需将为q找零的面值组合中的Va用为(Va)-1找零的面值组合代替,即可表示q-1
综上可得,q-1肯定可以被B找零。由于A包含B,所有q-1可以被A找零。
根据数学归纳法,可知q-2,...2,1均可以被A找零。得证。

接下来我们进入正题。

什么情况下取不大于且最接近sum的Vm的做法是正确的呢?事实上,在1, 2, ...,sum-1都可以被找零的情况下,若sum可以被小于Vm的一组面值集表示,则sum必定可以被包含Vm的一组面值集表示。

可以转化为对如下命题的证明:面值集G={ V1, V2, ..., Vn },sum=1, 2, ...,k-1(k-1>=1)都可以被正确找零。G中最接近且不大于sum的面值为Vm。已知sum可以被最大为Vr的面值集找零,求证若V(r+1)<=sum,则sum可以被最大为V(r+1)的面值集找零。则根据数学归纳法,sum可以被最大为Vm面值集合找零。

证明:

由于sum可以最大为Vr的面值集合找零,令sum=Vr+(sum-Vr),由于最大为Vr,所以(sum-Vr)可以被小于Vk的集合J={ V1, V2, ...V(k-1)}找零。
由命题2可知小于(sum-Vr)的自然数均可以被J找零。由于(sum-V(r+1))<(sum-Vr),因此(sum-V(r+1))可以被J找零。
由于(sum-V(r+1))被J找零的面值组合中肯定不包含V(r+1),因此sum可以表示为sum=V(r+1)+(sum-V(r+1)).即(sum-V(r+1))被J找零的组合再加上V(r+1)。
因此,若sum可以被最大为Vr的面值集找零,如果V(r+1)<=sum,则sum可以被最大为V(r+1)的面值集找零。
得证。

复杂度

(待续)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐