【问题标题】:What's the best way to evenly split a groups total?平均分配一组总数的最佳方法是什么?
【发布时间】:2018-08-10 07:07:43
【问题描述】:

我试图将总金额平均分配给每个相关人员。

例如我会用钱。

示例 1

A 有 20 美元

B 有 40 美元

C 有 60 美元

因此,为了解决所有问题,解决方案是 C 人给 A 人 20 美元。

示例 2

A 有 36 美元

B 有 15 美元

C 有 9 美元

为了让这种情况变得平衡......

A 给 B 16 美元,然后 B 给 C 11 美元,

或者甲给乙5美元,丙给丙11美元

示例 3

A 有 53 美元

B 有 95 美元

C 有 24 美元

D 有 98 美元

E 人有 30 美元

每个人需要 60 美元,我怎样才能找到涉及最少转移资金的方法?

【问题讨论】:

标签: javascript node.js algorithm


【解决方案1】:

一个自然的做法是:多给少的人;然后重复。

具体来说:您可以计算每个人最后应该拥有的金额 V(这只是每个人起始金额的平均值)。然后,如果拥有最多的人拥有 M,而拥有最少的人拥有 L,则从拥有最多的人到拥有最少的人,给出 min(M-V,V-L)。在那次移动之后,这两个人中至少有一个人的数量是正确的。现在重复,直到每个人都有所需的数量。

移动次数最多为人数。

我怀疑这可能是最佳的,但您应该自己检查一下。您可以尝试应用https://cs.stackexchange.com/q/59964/755中的方法,看看是否可以找到反例或证明它是正确的。

【讨论】:

  • 感谢您的建议,我采用了这种方法,效果很好!
【解决方案2】:

不回答这样一个问题似乎很可惜,因为它并不难解决 - 让我们使用你的最后一个例子:

  • A 有 53 美元
  • B 有 95 美元
  • C 有 24 美元
  • 人 D 有 98 美元
  • E 人有 30 美元

为了简单起见,我将使用数组。

const totals = [53, 95, 24, 98, 30]
let sum, target, moveCounter = 0

if (totals.length) {
  sum = totals.reduce(function(a, b) { return (a + b) })
  target = sum / totals.length
}

目标只是总数的平均值,在示例中为 60 美元。

let needs = totals.map(function(a){ return target - a })
// needs = [7, -35, 36, -38, 30]

现在您知道每个needs 是什么。

[0] 需要得到 7 美元, [1] 需要降低 35 美元, [2] 需要得到 36 美元, 等等

数组是零和:(7) + (-35) + (36) + (-38) + (30) = 0

您的问题是如何在尽可能少的移动中使每个元素都为 0。

您可以检查是否有任何元素相互抵消,例如 [20, -20]。一个有效的检查方法是创建一个 Set。

let unmatched = new Set([])
let matches = []
needs.forEach((need) => {
  if (0 !== need) {
    let value = Math.abs(need)
    if (unmatched.has(value)) {
      matches.concat([need, -need])
      unmatched.delete(value)
    } else {
      unmatched.add(value)
    }
  }
})

if (matches.length) {
  matches.forEach((matchElement) => {
    let index = needs.findIndex((needElement) => {
      return matchElement === needElement
    })
    if (index) {
      needs[index] = needs[index] - matchElement
      moveCounter = moveCounter + 1
  })
}

此时所有简单的动作都已完成,您可以对数组进行排序并暴力破解其余的。排序让事情变得更容易一些。

var sortedNeeds = needs.sort(function(a, b){ return a < b ? -1 : 1 })
// [-38, -35, 7, 30, 36]

数组是零和,因此您可以将其分成两部分 leftright。把它想象成一个平衡方程的左右两边。

const zeroIndex = sortedNeeds.findIndex((value) => { return 0 === value })
// zeroIndex : -1

const positiveIndex = sortedNeeds.findIndex((value) => { return value > 0 })
// positiveIndex : 2

const leftLimit = (zeroIndex > -1) ? zeroIndex : positiveIndex
// leftLimit : 2

let left = sortedNeeds.slice(0, leftLimit)
// left: [-38, -35]

let right = sortedNeeds.slice(positiveIndex)
// right: [7, 30, 36]

下一段是魔法:

moveCounter = moveCounter + (left.length - 1) + right.length
// 4 = 0 + 1 + 3

如何计算并不重要,因为零和关系是关键。

  1. D 给 B 38 美元
  2. B 给 A 7 美元
  3. B 给 C 36 美元
  4. B 给 E 30 美元

【讨论】:

  • 所以对于这个数据hastebin.com/elonudekaz.nginx 你的方法返回 5 步,当它可以在 4 中完成时,我不确定如何检查。
  • 您可以添加另一个级别的检查 - 如果您的 target 均匀地适合其中一个项目,那么当然可以用更少的动作完成。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-19
  • 2013-06-25
  • 2010-10-10
  • 2020-11-21
  • 1970-01-01
相关资源
最近更新 更多