题目描述

✅ 292. Nim 游戏

image-20260928223203253

image-20260928223203255

题意分析

桌面上有 n 块石头,两人轮流取走石头,你先手。每次必须取一到三块,取到最后一块的人获胜;判断双方都采用最优策略时,你是否一定能赢。

这里判断的是是否存在必胜策略,不能依赖对手犯错,也不是要求每次都取最多的三块。题目保证 n 为正数,只需要返回胜负,不需要模拟整场游戏。

解法:数学结论

核心思路

[!blue]

把轮到某人行动时的剩余石头数看作一个局面。能通过一次合法选择把对手送到必败局面的,是必胜局面;无论怎么选都会给对手留下必胜局面的,就是必败局面。

剩下一到三块时,当前玩家可以直接拿完获胜。面对四的倍数时,先行动的一方无论拿走 t 块,另一方都可以再拿 4 - t 块;两次合计恰好拿走四块,而且补取数量仍在一到三的合法范围内。

这样,每轮结束后都把更小的四倍数留给原先行动的一方。最后剩四块时,原先行动者仍拿不完,采用补足策略的一方会拿走最后一块。因此面对四倍数的玩家无法在对手最优时获胜。

若 n 不是四的倍数,余数 r = n % 4 必为一到三。先手先拿走这 r 块:若已经拿完就直接获胜,否则给对手留下四的倍数。随后对手每次取 t 块,自己就取 4 - t 块,保持同一策略直到拿到最后一块。

所以胜负只由是否为四的倍数决定,返回 n % 4 != 0。周期中的四来自“一次对手行动与一次回应合计四块”,不是最大单次取量三。

解题步骤

  1. 计算 n 对四的余数。
  2. 余数非零,先手可以取走余数并保持补足策略,返回真。
  3. 余数为零,对手可以保持补足策略,返回假。

代码实现

class Solution {
    // 如果先手面对 4 的倍数,无论拿 1、2、3 块,对手都能补到 4,让先手再次面对 4 的倍数。
    public boolean canWinNim(int n) {
        return n % 4 != 0;
    }
}
func canWinNim(n int) bool {
    // 如果先手面对 4 的倍数,无论拿 1、2、3 块,对手都能补到 4,让先手再次面对 4 的倍数。
    return n%4 != 0
}

复杂度分析

  • 时间复杂度:$O(1)$,只做一次取余与比较,不随石头数模拟回合。
  • 空间复杂度:$O(1)$,不保存博弈状态表。

关键点总结

[!green]

  • 最优博弈要判断能否把必败局面交给对手,不能只考察某种固定取法。
  • 四倍数是必败态,其他正数都能一步到达它或直接结束游戏。
  • 结论依赖每次取一到三块以及拿最后一块获胜这两条规则。

易错点总结

[!yellow]

  • 把四倍数当成必胜态,会把“留给对手的局面”与“自己面对的局面”混淆。
  • 只按奇偶判断,无法描述每次有三种合法选择时的胜负周期。
  • 以最大可取量三为模数,忽略了双方两次行动可以合计为四的回应策略。
  • 认为必须始终取三块,会错过根据余数和对手行动调整取法的必胜策略。

相似题目

题目 难度 关联与区别
1510. 石子游戏 IV 困难 同样是轮流取石子的必胜态分析,原题允许取平方数,不能直接套本题按4取模的规律。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2025/56742966
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!