分红包算法 | 青训营笔记

544 阅读2分钟

这是我参与「第四届青训营 」笔记创作活动的的第4天

聊聊我在字节青训营学到的分红包算法,需求也很常见,给个金额数量,然后所有人抢红包,每个人抢到的红包数都是随机的。

基础结构

要实现与测试这一功能,需要写一些 HTML 和 JS 结构,这并不是本文的重点,就直接放代码了

HTML

<div class="app">
  <div>
    红包金额:
    <input id="money" type="number" value="100" />
  </div>
  <div>
    红包数量:
    <input id="count" type="number" value="10" />
  </div>
  <button id="btn">分发红包</button>
  <pre id="list"></pre>
</div>

JS

const input1 = document.getElementById('money')
const input2 = document.getElementById('count')
const btn = document.getElementById('btn')
const list = document.getElementById('list')

btn.addEventListener('click', () => {
  const money = parseFloat(input1.value)
  const count = parseInt(input2.value)
  if (money * 100 < count) {
    list.innerText = '金额不足'
    return
  }
  let arr = distribute(money, count)
  let res = ''
  for (const num of arr) {
    res += (num / 100).toFixed(2) + '\n'
  }
  list.innerText = res
})

function distribute(money, count) {
  money *= 100
  return new Array(count).fill(money / count)
}

本文的核心在于实现 distribute 函数,目前该函数只能平分红包,我们需要它能将金额随机分配

随机分红包

说到随机,第一时间想到的肯定是下面这样

function distribute(money, count) {
  money *= 100
  let res = []
  while (--count) {
    let m = (money * Math.random()) | 0
    res.push(m)
    money -= m
  }
  res.push(money)
  return res
}

这样实现存在问题,有人可能会抢到 0.00 的红包,显然是不合理的。

切西瓜

为了剩余的红包足够分,那我们每次都只分大的那部分,不就好了

就像生活中的切西瓜,每一刀切下去,只切最大的那部分

实现也很简单,找到数组中最大的那项切就好了,代码如下

function distribute(money, count) {
  money *= 100
  let res = [money]
  while (--count) {
    let maxMoney = -1,
      maxIndex = 0
    for (let i = 0; i < res.length; i++) {
      if (res[i] > maxMoney) {
        maxMoney = res[i]
        maxIndex = i
      }
    }
    let ran = Math.max((maxMoney * Math.random()) | 0, 1)
    res.splice(maxIndex, 1, ran, maxMoney - ran)
  }
  return res
}

放隔板

上面那种方法成功解决了红包不够分的问题,但因为每次分的都是最大的那个,大伙的红包趋于平衡,很难有 30 以上的红包,不够刺激

所以我们改造一下最开始失败的那个方法,不是纯随机地取值,而是在不同的位置放隔板

比如想把 100 元分成十份,就是在其中放 9 个隔板,隔板的取值范围为 1~9999(100元 = 10000分)

我们借助数组来确保每次随机的值不同,代码如下

function distribute(money, count) {
  money *= 100
  let arr = new Array(money).fill(0).map((_, i) => i)
  while (--count) {
    let ran = Math.floor((money - 2) * Math.random()) + 1
    let temp = arr[count]
    arr[count] = arr[ran]
    arr[ran] = temp
  }
  let res = arr.slice(1, 10)
  res.sort((a, b) => a - b)
  res.push(money)
  for (let i = res.length - 1; i > 0; i--) {
    res[i] = res[i] - res[i - 1]
  }
  return res
}

该方法唯一的不足就是增加了空间复杂度,可是大家又能发多大的红包呢?

结语

如果喜欢或有所帮助的话,希望能点赞关注,鼓励一下作者。

如果文章有不正确或存疑的地方,欢迎评论指出。