题目描述
现给定n个物品,其概率分别为P1, P2, P3...Pn,请设计一个接口,根据概率,随机获取一个物品。
算法分析
本题的算法可以使用《编程珠玑》中的算法实现,将概率抽象成线段,而后使用rand()%区间总长度,根据随机数所在的区间,确定选定哪个物品;现假设n=5,P1=10,P2=40,P3=5,P4=20,P5=25,那么将其转换成线段后如下图所示:
随机获取物品的情况如下图所示:
以上是假设概率均为整数的情况,对于概率有小数的情况,则根据其要保留的小数位数k,将其概率乘以10^k,并相应的将线段的总长度进行相应倍数的扩展。
一些问题
先验条件
- P1+P2+...+Pn=100%
- 0<=Pi<=1(1<=i<=n)
- n>0
可扩展性
对于需求变化小的情况,可以直接if-else if-...-else进行枚举,但是如果需求变动大,或者需要经常修改物品数量,就不能使用此种方法了。解决方案是动态计算,见时间复杂度VS空间复杂度小节。
时间复杂度VS空间复杂度
在采用动态计算时,可以指定一个数组,其内部存储类型为struct Range { int start; int end };在使用rand()%区间总长度获取随机数后,枚举此数组中的区间,即可获得相应的物品。此种方案可以有效的节约内存,但是每次调用rand()后,都需要做一次线性的查找,效率较低;
在现实世界中,此题目一般对应的案例为抽奖程序,那么就需要多次频繁地调用此函数,而且其运行在server端,内存并不是瓶颈,因此可以采用以空间换时间的办法来进行业务逻辑相关的优化。其算法如下:
依然假设n=5,P1=10,P2=40,P3=5,P4=20,P5=25。那么,就分配一个长度为100的数组,将物品区间填充成1,[10,50)区间填充成2,...,[75,100)区间填充成5(此过程只需进行一次,以后调用此函数无需重复填充),这样使用rand()%区间总长度获取随机数后,可以直接使用array[随机数]进行物品的获取,例如array[20]==2,其时间复杂度为O(1)。
经过以上的对比分析,可以得出在现实世界中,采用空间换时间的方案是最优解。
总结
解决此类题的时候,要注意题目中隐含的先验条件,还要注意接口的易用性(例如参数应该以什么形式传入/传出),还要取舍函数的可重入性,以及代码与业务逻辑之间的联系。