算法题:抢红包

348 阅读2分钟

题目描述

给定一个金额和人数,实现微信抢红包算法。

隐含条件,不能有人是空红包,即每个人最少分得0.01元,同时会确保输入数据合理(微信红包人均低于0.01发不出来)。

问题

由于最少需要0.01元,浮点数随机的时候不太好处理,可以直接金额乘上100,这样分完以后再除以100。另外就是考虑如何随机更公平。

参考

博客中一共提出了四种算法,这里自己又实现了一遍。

  • 第一种,纯随机法,会出现先领的人领到的多,后领的人得到的少的问题,该解法不好。

  • 第二种,线段法,将总金额想象成一条那么长的线段,需要分割成num份,随机num-1次,将每次的随机值映射到该线段上。个人觉得相对比较公平,但是存在一个问题,如果发的红包比较小,比如9个人分0.1元,该方法效率有点低,因为要防止出现重复的点。

  • 第三种,双倍随机法,每次随机的时候,取0-每个人平均金额的2倍进行随机。这种方法有一个问题,不会有人分到超过平均金额的2倍的钱。

  • 第四种,投票法(投篮法),即规定一个最小金额,比如0.01元,每次将最小金额随机给一个人。当然,可以先给每个人分0.01元,这样就不会出现有人没分到的情况。这种方法确实比较随机,但计算量稍大,而且只要人均金额稍微大一点,就会出现分布过于平均的情况。个人觉得可以在人均比较小的场景下用,比如9个人分0.1元。

代码

#include<bits/stdc++.h>

using namespace std;

// 纯随机法
void red_packet1(int money_i, int num, vector<int> &res) {
    int min_i = 1; // 每个人最少一分钱
    int max_i = money_i - min_i * (num - 1); // 每个人最多可以分到的钱

    int temp;
    for (int i = 0; i < num - 1; ++i) {
        temp = rand() % max_i + min_i;
        res.push_back(temp);
        money_i -= temp;
        max_i = money_i - min_i * (num - i - 1);
    }
    res.push_back(money_i);
}

// 线段法
void red_packet2(int money_i, int num, vector<int> &res) {
    set<int> m_set;
    int temp;
    for (int i = 0; i < num - 1; ) {
        temp = rand() % (money_i-1) + 1;
        if (!m_set.count(temp)) { // 去重
            i++;
            m_set.insert(temp);
        }
    }
    int last = 0;
    // set已经排好序,直接取值计算即可
    for (auto item:m_set) {
        res.push_back(item - last);
        last = item;
    }
    res.push_back(money_i - last);
}

// 双倍随机法
void red_packet3(int money_i, int num, vector<int> &res) {
    res.resize(num, 1); // 设置保底,每个人最少1分钱
    money_i -= 1 * num;
    int base_m = money_i / num * 2;
    int temp;
    for (int i = 0; i < num - 1; ++i) {
        if (money_i == 0)
            break;
        if (money_i >= base_m)
            temp = rand() % base_m;
        else
            temp = rand() % money_i;
        res[i] += temp;
        money_i -= temp;
    }
    res[num-1] += money_i;
}

// 投票法
void red_packet4(int money_i, int num, vector<int> &res) {
    res.resize(num, 1); // 设置保底,每个人最少1分钱
    money_i -= 1 * num;
    for (int i = 0; i < money_i; ++i) {
        res[rand() % num]++;
    }
}

int main( )
{
    srand(time(NULL)); // 设置随机种子

    double money = 10;
    int num = 10;
//    cin >> money >> num;
    int money_i = money * 100; // 扩大100倍,便于随机处理

    vector<int> res;
//    red_packet1(money_i, num, res);
//    red_packet2(money_i, num, res);
//    red_packet3(money_i, num, res);
    red_packet4(money_i, num, res);

    cout << "result sum:" << (double)accumulate(res.begin(), res.end(), 0) / 100 << endl ; // 验证总金额
    for (auto item: res) {
        printf("%.2f ", (double)item / 100);
    }
    cout << endl;
}