题目:我们做了一个活动,根据用户的积分来抽奖,用户的积分都保存在一个数组里面
arr = [20, 34, 160, 2…],数组下标就是用户的 ID,则这里:
ID 为 0 的用户的积分是 arr[0] 等于 20 分。
ID 为 1 的用户的积分是 arr[1] 等于 34 分。
请你设计一个抽奖算法,随机抽出一位中奖用户,要求积分越高中奖概率越高。
返回值是中奖用户的 ID
PS: 1<= arr.length <= 50000 且 1<= arr[i] <= 50000
代码写出算法,
并分析其时间复杂度,
为其编写尽量多 unit test。
import java.util.Random;
import java.util.Scanner;
/*思路变化
关于权重首先想到霍夫曼树,构造哈夫曼树算法比较复杂,但是我们通过生成霍夫曼编码匹配前缀应该算法应该比较公平吧,毕竟只有0和1;现的话,30分钟肯定不够。
用户次数问题,根据位数或者份额给次数,但是次数肯定是近似值,不够公平;
奖品的占比修改,这个要判断用户积分改变奖池的概率,实现比较麻烦,而且只能说相对公平;
邻接表,最后的思路
*/
//用时说明,算法实现费时30分钟,主要是想法比较长,还有对算法说明的部分,总耗时间一个小时。
//参考链接: https://blog.youkuaiyun.com/weixin_44484674/article/details/121540132
//PS: 1<= arr.length <= 50000 且 1<= arr[i] <= 50000
/*此算法思想:
把总积分当成一个从0到积分和的线段,各id下的积分代表第id+1段的长度,随机数落点在哪段就是所在id中奖
一开始想的是邻接表数据结构+随机数类实现目标;
邻接表是数组加链表的方式,链表存储的是所占起点和终点;
后面发现转换一下数组表示就好,即每个id下存终点,且此终点是下一个id的起点,统一遵循左闭右开吧。
实现如下:*/
public class Come {
//设数组的长度为n
public static void main(String[] args) {
Random rd=new Random();
//手动输入用户的积分
// Scanner sc=new Scanner(System.in);
// int n=sc.nextInt();
// int[] arr=new int[n];
// for (int i = 0; i < n; i++) { arr[i] = sc.nextInt(); }
// int[] arr1=switchArr(arr);
//有初始值的测试
int[] arr=new int[]{20,34,160,25};
int[] arr1=switchArr(arr); //时间复杂度O(n)
// 打印出来观察结果
for (int i = 0;i < arr1.length; i++) { System.out.print(arr1[i]); }
int max=arr1[arr1.length-1]+1;
int rand=rd.nextInt(max);//把总份数当成一条线段,随机数落点是等概率的
// 观察生成的随机数,多运行几次就是测试几次
System.out.println(rand);
//返回id,寻找id
int id=findId(rand,arr1);//时间复杂度O(n)
// 打印中奖用户id
System.out.println(id);
}
//生成n个随机数代表用户的份额
public static int[] switchArr(int[] arr) {
int[] arr1=new int[arr.length];
for (int i = 0; i <arr.length; i++) {
for (int j = 0; j <=i; j++) {
arr1[i]+=arr[j];
}
}
return arr1;
}
//返回id
public static int findId(int rand,int[] arr1) {
int id=0;
for (int i = 0; i < arr1.length; i++) {
if(rand>=arr1[i]&&rand<arr1[i+1]) {
id=i+1;
break;//找到了id退出循环
}
}
return id;
}
}