数据结构---红包分配算法

问题如下:

  1. 所有人抢到的金额之和要等于红包金额,不能多也不能少。
  2. 每个人至少抢到1分钱。
  3. 要保证红包拆分的金额尽可能分布均衡,不要出现两极分化太严重的情况。

错误解法

每次拆分的金额 = 随机区间[1分, 剩余金额-1分]

存在的问题:

  1. 第1个人抢到金额的随机范围是[0.01,99.99]元,在正常的情况下,抢到金额的中位数是50元。假设第1个人随机抢到了50元,那么剩余金额是50元。
  2. 第2个人抢到金额的随机范围就小得多了,只有[0.01,49.99]元,在正常的情况下,抢到金额的中位数是25元。假设第2个人随机抢到了25元,那么剩余金额是25元。
  3. 第3个人抢到金额的随机范围就更小了,只有[0,24.99]元,按中位数可以抢到12.5元。
  4. 以此类推,红包的随机范围将会越来越小,不符合红包拆分的金额尽可能分布均衡的需求。。。

二倍均值法

把每次随机金额的上限定为剩余人均金额的2倍。

红包金额为m元
剩余人数为n
每次抢到的金额 = 随机区间 [0.01,m /n × 2 - 0.01]元

保证了每一次抢到金额随机范围的均值是相等的,不会因为抢红包的先后顺序而造成不公平。

  1. 第1次随机的金额有一半概率超过20元,使得后面的随机金额上限不足39.99元;
  2. 第1次随机的金额同样也有一半的概率小于20元,使得后面的随机金额上限超过39.99元。
  3. 因此从整体来看,第2次随机的平均范围仍然是[0.01,39.99]元。

JAVA实现

    /**
     * 分配红包的金额
     * @param totalAmount     总金额(以分为单位)
     * @param totalPeopleNum  总人数
     * @return
     */
    public static List<Integer> divideRedPackage(Integer totalAmount,Integer totalPeopleNum){
        List<Integer> amountList = new ArrayList<Integer>();//存储分配的不同红包大小的数值
        Integer restAmount = totalAmount;
        Integer restpeopleNum = totalPeopleNum;
        Random random = new Random();
        //最后一个人不用随机分配了,之间差值
        for (int i=0;i<totalPeopleNum-1;i++){
            //随机范围:[1,剩余人均金额的2倍-1] 分
            //+1d的原因是:random.nextInt随机返回一个值在[0,num)的int类型的整数,包括0不包括num
            //random.nextInt+1的范围是[1,num+1)也就是[1,num]
            //就满足了随机区间: [1 , m /n × 2 - 1]分
            int amount = random.nextInt((restAmount/restpeopleNum)*2-1)+1;
            restAmount = restAmount-amount;
            restpeopleNum--;
            amountList.add(amount);
        }
        amountList.add(restAmount);
        return amountList;
    }

测试方法:

    public static void main(String[] args) {
        List<Integer> amountList = divideRedPackage(1000,10);
        for (Integer e:amountList){
            System.out.print(e+"  ");
        }
        System.out.println();
    }

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

局限性
除最后一次外,其他每次抢到的金额都要小于剩余人均金额的2倍,并不是完全自由地随机抢红包。(随机数值的上限被限制死了

线段切割法

把红包总金额想象成一条很长的线段,而每个人抢到的金额,则是这条主线段所拆分出的若干子线段。

确定每一条子线段的长度

由“切割点”来决定。
当n个人一起抢红包时,就需要确定n-1个切割点。

当所有切割点确定以后,子线段的长度也随之确定。此时红包的拆分金额,就等同于每个子线段的长度

当n个人一起抢总金额为m的红包时,我们需要做n-1次随机运算,以此确定n-1个切割点。
随机的范围区间是[1, m-1]。
开始1和结束是m-1的原因是最少分配1分钱(这里1表示一分)
[0,1]一段,[m-1,m]一段(最小的情况)

需要考虑的问题:

  1. 当随机切割点出现重复时,如何处理。
  2. 如何尽可能降低时间复杂度和空间复杂度。

JAVA实现

HashMap和HashSet区别HashSet存取值

/**
     * 拆分红包V2
     * @param totalAmount  总金额(以分为单位)
     * @param totalPeopleNum  总人数
     */
    public static List<Integer> divideRedPackageV2(Integer totalAmount, Integer totalPeopleNum){
        List<Integer> amountList = new ArrayList<Integer>();
        Set<Integer> segments = new HashSet<Integer>();
        Random random = new Random();
        for(int i = 0; i< totalPeopleNum-1; i++){
            //切割的位置segment,随机的范围区间是[1, m-1]
            int segment =  random.nextInt(totalAmount-2) + 1;
            //delta == 1
            int delta = random.nextInt(1)==0 ? 1 : -1;
            //这是异常的情况
            // segments.contains(segment) ==true 表示切割位置重复
            //segment == 0,切割位置不正确
            while(segments.contains(segment) || segment == 0){
                //加上1位,用来解决以上俩个问题。
                //%totalAmount是解决超过线段长度的情况。
                segment = (segment+delta)%totalAmount;
            }
            segments.add(segment);
        }

        //依据切割的段来划分 钱数
        //从HashSet取值
        //Collections.sort排序方法,把切割位置从小到大排序。
        List<Integer> segmentList = new ArrayList<Integer>(segments);
        Collections.sort(segmentList);
        for(int i=0; i<segmentList.size(); i++){
            Integer amount;
            if(i==0){
                amount = segmentList.get(0);
            }else {
                amount = segmentList.get(i) - segmentList.get(i-1) ;
            }
            amountList.add(amount);
        }
        amountList.add(totalAmount - segmentList.get(segmentList.size()-1));

        return amountList;
    }

测试方法:

    public static void main(String[] args) {
        List<Integer> amountList = divideRedPackageV2(1000,10);
        for (Integer e:amountList){
            System.out.print(e+"  ");
        }
        System.out.println();
    }

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

保证了每次划分都是随机,并且概率相同,并且没有最大限制。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,这里提供一个简单的红包雨功能的object-c实现示例: 首先,我们需要准备红包图片的资源,用UIImageView来展示红包。 ```objective-c // 初始化红包图片视图 UIImageView *redPacketImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, -50, 50, 50)]; redPacketImageView.image = [UIImage imageNamed:@"redPacket.png"]; [self.view addSubview:redPacketImageView]; ``` 接下来,我们需要实现红包的下落动画,让它从上面飘落到下面。 ```objective-c // 定义红包下落动画 CABasicAnimation *fallingAnimation = [CABasicAnimation animationWithKeyPath:@"position.y"]; fallingAnimation.fromValue = [NSNumber numberWithFloat:-50]; fallingAnimation.toValue = [NSNumber numberWithFloat:self.view.bounds.size.height + 50]; fallingAnimation.duration = 5.0; fallingAnimation.delegate = self; // 添加红包下落动画 [redPacketImageView.layer addAnimation:fallingAnimation forKey:@"fallingAnimation"]; ``` 我们同时需要处理多个红包的下落逻辑,可以使用NSTimer定时器来实现。 ```objective-c // 定义红包定时器 NSTimer *redPacketTimer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(addRedPacket) userInfo:nil repeats:YES]; // 添加红包方法 - (void)addRedPacket { // 初始化红包图片视图 UIImageView *redPacketImageView = [[UIImageView alloc] initWithFrame:CGRectMake(arc4random_uniform((uint32_t)self.view.bounds.size.width - 50), -50, 50, 50)]; redPacketImageView.image = [UIImage imageNamed:@"redPacket.png"]; [self.view addSubview:redPacketImageView]; // 定义红包下落动画 CABasicAnimation *fallingAnimation = [CABasicAnimation animationWithKeyPath:@"position.y"]; fallingAnimation.fromValue = [NSNumber numberWithFloat:-50]; fallingAnimation.toValue = [NSNumber numberWithFloat:self.view.bounds.size.height + 50]; fallingAnimation.duration = 5.0; fallingAnimation.delegate = self; // 添加红包下落动画 [redPacketImageView.layer addAnimation:fallingAnimation forKey:@"fallingAnimation"]; } ``` 最后,我们需要处理红包落地后的逻辑,让用户可以获得奖励。 ```objective-c // 红包下落动画结束后回调方法 - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag { // 判断是否是红包下落动画 if ([redPacketImageView.layer animationForKey:@"fallingAnimation"] == anim) { // 移除红包图片视图 [redPacketImageView removeFromSuperview]; // 弹出获得奖励提示框 UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"恭喜您获得奖励!" message:@"获得了10元现金红包!" preferredStyle:UIAlertControllerStyleAlert]; [alertController addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:nil]]; [self presentViewController:alertController animated:YES completion:nil]; } } ``` 这样就实现了一个简单的红包雨功能。当用户进入红包雨页面后,红包会不断从上面飘落到下面,并且用户可以获取奖励。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值