深蓝黑背景封面。左侧是一条自上而下的逆向归纳推理链,四张圆角卡片以向下箭头串联:第一张「只剩 D、E:D 独吞 100,E 得 0」,注「E 反对也只是半数否决,提案照样过」;第二张「C 提案:给 E 1 枚,C 拿 99」,注「E 知道下一轮自己得 0,1 枚就动心」;第三张「B 提案:给 D 1 枚,B 拿 99」,注「D 下一轮得 0,同样 1 枚收买」;第四张以青色高亮「A 提案:给 C、E 各 1 枚」,注「A 自己留下 98 枚——这就是答案」。底部一句话「票价逻辑:只收买下一轮拿 0 的人,每人 1 枚就够」。右侧大标题「5 个人分 100 枚金币,第一个怎么分才拿最多」,副标题「逆向归纳:从终点倒推每一步最优」。左上标注「面试经典 · 算法」,右上圆角胶囊标注「Day 01 · 11-08」 图 1:本文封面,四步倒推链的答案在最后一环。

先说明属性:这是一篇经典题解析,题目出自本地历史面试资料,与任何公司的当前招聘无关。「5 个人分 100 枚金币」是博弈论逆向归纳的招牌题,很多人背过答案却讲不清过程,面试官要的恰恰是那条倒推链。

先把规则钉死,差一个字答案就变。

5 个人按等级 A、B、C、D、E 依次提案分配 100 枚金币。规则:提案被过半否决,提案者扔下海,由下一人接着提案;否决未过半——包括恰好一半人反对——提案通过。还有三个隐含假设:人人绝对理性,只为最大化自己的收益;收益相同时宁可投反对(看别人下海不花自己一分钱);所有人都知道别人也这么想。正是「平票算通过」这一条,决定了后面每一步的票价,答题前务必先和面试官确认。

从终点倒推:先算只剩两个人的残局。

正向思考「A 该给谁分多少」会想晕,逆向归纳的做法是先站到游戏终点,再一层层往回走。

最小残局是只剩 D、E 两人。D 提案,自己投一张赞成票;E 就算反对,反对票也只有 1 张,恰好半数、未过半,提案照样通过。所以 D 独吞 100,E 得 0——这是整条推理链的地基。

每加一个人,重算一次「下轮所得」。

C 加入后剩三人。过半否决需要 2 张反对票,所以 C 自己赞成之外,只需再买 1 张票。买谁?看谁下一轮拿得最少:残局里 E 得 0,给 1 枚就比下轮强;D 下轮独吞 100,根本买不起。于是方案是 C=99、D=0、E=1,反对票只有 D 一张,提案通过。

B 加入后剩四人。过半否决需要 3 张反对票,B 自己赞成后再买 1 张票就够。上一轮的分配是 C=99、D=0、E=1,最便宜的是拿 0 的 D,给 1 枚收买。方案 B=99、C=0、D=1、E=0,C 和 E 反对也只有 2 票,恰好半数,提案照过。

回到全局:A 的最优解。

A 提案时 5 人投票,过半否决需要 3 张反对票,A 自己赞成后再买 2 张票即可。上一轮 B 的方案里,C 和 E 都得 0,正好是全场最便宜的两张票,各给 1 枚。最终方案:A=98、B=0、C=1、D=0、E=1。反对票只有 B 和 D 两张,提案通过——先手 A 用 2 枚金币买下关键票,自己留下 98 枚。

注意一个常见误答是 A 留 97:那是把「买下两票」的账算错了。98+1+1 正好 100,每一枚都对得上。

用代码把每一步验证一遍。

下面这段程序把倒推过程一般化:从 1 人残局开始,每加一人就按「否决不超过半数」算出需要收买的票数,再从便宜到贵买下「下一轮拿得最少」的人。用 Node.js 22.15.0 运行(node pirates.js):

function solve(n, total = 100) {
  let dp = [[total]]; // 只剩 1 人:独吞
  for (let k = 2; k <= n; k++) {
    const prev = dp[k - 2];                    // 剩 k-1 人时的分配
    const maxAgainst = Math.floor(k / 2);      // 否决数须 <= floor(k/2)
    const buyCount = k - maxAgainst - 1;       // 扣掉提案者自己的赞成票
    // 收买成本 = 此人下一轮所得 + 1,从便宜到贵排序
    const cost = prev.map(v => v + 1)
      .map((c, i) => [c, i]).sort((a, b) => a[0] - b[0]);
    const alloc = new Array(k).fill(0);
    let spent = 0;
    for (let t = 0; t < buyCount; t++) {
      alloc[cost[t][1] + 1] = cost[t][0];
      spent += cost[t][0];
    }
    alloc[0] = total - spent;
    dp.push(alloc);
  }
  return dp;
}
const names = ['A', 'B', 'C', 'D', 'E'];
for (const [i, alloc] of solve(5).entries()) {
  const people = names.slice(5 - (i + 1));
  console.log(`剩 ${i + 1} 人(${people.join('、')}):` +
    alloc.map((v, j) => `${people[j]}=${v}`).join(' '));
}

实测输出:

剩 1 人(E):E=100
剩 2 人(D、E):D=100 E=0
剩 3 人(C、D、E):C=99 D=0 E=1
剩 4 人(B、C、D、E):B=99 C=0 D=1 E=0
剩 5 人(A、B、C、D、E):A=98 B=0 C=1 D=0 E=1

输出与手工倒推逐行吻合,最后一行就是答案。

深蓝黑背景概念图。主体是一张五行分配矩阵:列为剩余局面与 A、B、C、D、E 五人各得金币数及凑票逻辑;第一行「只剩 D、E」时 D 得 100、E 得 0,注「D 自己一票就够,独吞」;第二行 C 加入后 C 得 99、D 得 0、E 得 1,注「E 下轮得 0,给 1 枚收买」;第三行 B 加入后 B 得 99、D 得 1,注「D 下轮得 0,给 1 枚收买」;第四行 A 加入的完整全局以青色描边高亮:A 得 98、C 得 1、E 得 1、B 与 D 得 0,注「C、E 下轮都得 0,各给 1 枚」。右侧两张卡片:规则卡「提案被过半否决,提案者扔下海,由下一人接着提案;否决未过半即通过,人人理性且都知道规则」;倒推三步卡「先算只剩 2 人的最小残局;每加 1 人重算每人下轮所得;用最便宜的票凑到否决不过半」。底部总结条:答案 A 留 98 枚、C 与 E 各 1 枚、B 与 D 一分没有——先手用最小成本买下关键票;另注启示:多智能体协商与拍卖机制里同样先算终局再决定当下出价 图 2:分配矩阵逐行展示了从 2 人残局到 5 人全局的倒推过程。

这道题真正的考点是推理方向。

题目本身半小时能背完,但它示范的「从终点倒推每一步最优」是策略推演的通用范式:多智能体协商里先算谈判破裂时各方的保底收益,再决定当下让步多少;拍卖机制设计里先算最后一轮的均衡出价,再反推首轮报价。看懂倒推,就看懂了这一类机制设计题的共同骨架。

回到面试现场,建议的答题结构:先确认规则细节(平票算不算通过、无差别时投哪边),再宣布方法「逆向归纳」,从 2 人残局逐层推回 5 人并给出 A=98 的分配,最后补一句工程启示。答出数字是及格线,讲清「收买对象永远是下一轮拿 0 的人、每人只值 1 枚」才是加分项。你第一次做这道题时,是顺着推还是倒着推的?有没有被「平票算不算过」坑过?欢迎讲讲。