1.总体概率算法: 必中奖的方式
将奖品A、B、C对应的概率值转换成阶梯范围值,A=(0~0.2」、B=(0.2-0.5」、C=(0.5-1.0」,当使用随机数方法生成一个随机数后,与阶梯范围值进行循环比对找到对应的区域,匹配到中奖结果。
步骤:
-
首先要从总的中奖列表中排除掉那些被排除掉的奖品,这些奖品会涉及到概率的值重新计算。
-
如果排除后剩下的奖品列表小于等于1,则可以直接返回对应信息
-
接下来就使用随机数工具生产一个100内的随值与奖品列表中的值进行循环比对,算法时间复杂度O(n)
2.单项概率(算法)
- 不涉及奖品概率重新计算的问题,那么也就是说我们分配好的概率结果是可以固定下来的。可以初始化一个概率元组数据
- 优化算法,根据概率值存放到HashMap或者自定义散列数组进行存放结果,这样就可以根据概率值直接定义中奖结果,时间复杂度由O(n)降低到O(1)。这样的设计在一般电商大促并发较高的情况下,达到优化接口响应时间的目的。
优化算法是使用斐波那契散列索引的方式初始化概率元组数据以及随机值的匹配中奖信息。
// 数组初始化长度 RATE_TUPLE_LENGTH = 128
20 * HASH_INCREMENT + HASH_INCREMENT)& (128-1)
斐波那契散列通过黄金分割点的增量计算,保证在最小空间中避免哈希碰撞。
PS:解决哈希冲突的其他方法
HashMap采用的 拉链法
- 当发生哈希碰撞的时候,依然会选择在这个槽位,这个槽位会维护一个链表或者其他数据结构(红黑树),会把这个元素添加到里面去。
- 适合用于解决频繁产生哈希冲突的场景。
- 适用于数据量不可预测的场景。在容器元素比较多的时,按 2次幂 进行扩容,很容易产生多余的空间。
ThreadLocal采用的 开放地址法
- 当遇到哈希冲突时,会再次进行 线性探测,寻找下一个空位。
- 线性探测:如果当前位置被占用,则依次往后查找,直到找到一个空槽位。
- 开放地址法每一个位置只能存一个元素,在相同元素的条件下,开放地址法的
table会比拉链法的table容量要大,所以开放地址法适合可以提前确定数据量的场景,拉链法适合数据量不可预测的场景。
为什么ThreadLocal选择了开放地址法而不是拉链法
存储 TheadLocal 的 ThreadLocalMap 的存储结构是:<ThreadLoacl, Object> 。key 是 ThreadLocal,,数量有限且受控,冲突概率低。相比起 HashMap 的 <Object, Object> 存储结构,ThreadLocalMap 发生哈希碰撞的几率是非常小的。
面试问题:
- 首先抽奖策略中,在中奖概率最低是1%的情况下,概率元组长128,那如果中奖概率是百万分之一的话(比如奖品是电脑),需要怎么处理呢,应该不是初始化一条长度为百万+的数组吧
- 把数据模拟到redis里,先把奖品散列到key上;空的key无奖品,就不用生成。这样就相当于只存了有奖品的key,当没中奖的时候,查询对应的key是不存在的,而中奖的时候,查询当前的key是存在的。
- 离散化算法:当需要处理一个拥有很大值域的数组,但用到的值却很小时,可以使用离散化有效降低时间复杂度。就是把奖品在一个很大数组中的下标,映射到一个很小数组中。
- 别名采样算法:通过构建两个表:概率表和别名表,实现从离散概率分布中生成随机样本的高效算法。其主要优点在于预处理之后,每次采样的时间复杂度为O(n) 。
设计一个算法,在一个不知道多少人的广场上随机挑选10个人,要求每个人被抽到的概率是等可能的?
答:蓄水池抽样算法的目标是在不知道总人数的情况下,确保每个人被选中的概率是等可能的。
-
初始化:
- 我们创建一个大小为
k的蓄水池,用于存储前k个元素。在你的问题中,k=10。
- 我们创建一个大小为
-
处理第
k+1个以及后续的所有元素:- 对于第
k+1个元素以及之后的每个新元素(假设当前处理到第i+1个元素),生成一个在0到i之间的随机数j。 - 如果
j的值小于k,则用这个新元素替换蓄水池中索引为j的元素。
- 对于第
-
如何回答为什么使用斐波那契抽奖算法,而不是直接用顺序表存储? 这两个算法所需的空间以及时间看起来是一样的,斐波那契额反而还多加了一层哈希查找
答:为了在减少数据空间使用功能的情况下增加散列效果,存放奖品时使用斐波那契计算索引,获取奖品时直接Random随机一个位置抽奖。
-
为什么要设置一个总的活动库存次数? 每次用户领取活动的时候都需要扣减这个总库存,但是一般在营销活动开启时间内,不是用户随时都可以参加活动吗,而且事先也不能确定有多少用户会参加活动? 如果非要加一个总次数限制的话,一般设置多少合适呢?这个总次数是基于奖品的库存量还是什么考虑的
答:是的,库存是控制运营成本上限的,运营人员会配置活动库存、奖品库存、兜底奖品、活动人群等,来做调整以让活动达到最佳的效果。所有的运营都是有运营目的性的,不是随便撒钱。
-
定时任务补偿MQ需要循环扫描表中的所有数据,如果随着业务增长,表的数据达到了亿级别的话,岂不是扫描一遍会很慢?这样的话可以怎么处理呢,还是说xxl-job扫描任务很快,不需要担心这个问题
答:分布式任务扫描数据可以查询出未处理任务的最大的id,之后根据id > ? 来查询,减少扫描数量。另外对于任务的处理,还可以设计一个单独的表,这样的就可以已经处理完的n个周的数据,进行迁移。
-
采用定时任务扫描的方式,如何保证活动准时开启和关闭呢?使用轮循的方式,感觉总是会有延迟的吧
答:1. 提前5分钟把活动扫描为活动中,在使用活动的时候,根据状态和时间个字段判断,如果设定的开始时间未到,则活动也展示为未开始。 2. 另外如果是一些必须精准处理的任务,可以使用延迟队列设计。 。
-
想要在项目里使用一点并发编程相关的知识,可以在哪里添加呀,比如说线程池,AQS锁什么的
答:1. 其实分布式的设计是尽可能降低对1台机器的压榨,而是把任务分散到多台实例上,所以你自己处理的业务是几乎没有非得用多线程的,而是用分布式算力替代了多线程。 2. 那么还有一些必须使用的场景,多数是池化类设计,比如;JDBC连接池、多线程数据查询汇总等。你可以举例说自己给APP提供的接口有多个接口需要汇总数据调用,暂时又不好调整数据结构,因而需要使用多线程来处理数据查询。