1901年,哈佛大学教授Charles L. Bouton在《数学年鉴》上发表了一篇论文,题为"Nim, A Game with a Complete Mathematical Theory"。这篇论文揭示了一个令人震惊的事实:一种看似需要随机应变的策略游戏,竟然可以用一个简单的数学公式完全预测胜负。这个公式就是——异或和(XOR sum)。
这个发现的意义远超游戏本身。它为组合博弈论奠定了基础,催生了Grundy数、Sprague-Grundy定理等重要理论,并最终演化为现代人工智能中的极大极小算法和Alpha-Beta剪枝。在LeetCode上,博弈论题目形成了一个独特的类别,从简单的Nim Game到复杂的Stone Game系列,考察的都是同一套核心思维。
公平组合游戏:博弈论的基石
博弈论算法研究的对象是"公平组合游戏"(Impartial Combinatorial Game)。这类游戏具有三个核心特征:
公平性:两名玩家可用的操作完全相同,游戏状态是公开的,不存在隐藏信息。谁先手谁后手是唯一的区别。
有限性:游戏必然在有限步内结束,不存在无限循环的可能。
确定性:游戏结局完全由玩家的决策决定,不存在掷骰子或抽牌等随机因素。
满足这三个条件的游戏,每个状态都可以被精确地划分为"必胜态"(N-position)或"必败态"(P-position):
- 必胜态:存在至少一种操作可以将对手送入必败态
- 必败态:无论采取何种操作,都会将对手送入必胜态
这个分类是博弈论所有算法的基础。判断一个状态是必胜还是必败,只需要递归地分析其后继状态。如果存在通往必败态的路径,当前状态就是必胜态;如果所有路径都通向必胜态,当前状态就是必败态。
Nim博弈:异或和的魔法
Nim是最经典的公平组合游戏。游戏规则极其简单:有若干堆石子,两名玩家轮流行动,每次可以从任意一堆中取出任意数量的石子(至少取一颗),取走最后一颗石子的人获胜。
假设当前有三堆石子,数量分别为3、4、5。谁有必胜策略?直觉上,这似乎需要分析大量可能的走法。但Bouton的论文给出了一个惊人的结论:只需计算所有堆大小的异或和,若结果非零则先手必胜,若为零则先手必败。
异或和的计算方法
异或(XOR)是一种位运算,规则是"相同为0,不同为1"。计算异或和时,将所有数字转换为二进制,然后逐位进行异或运算:
3 = 011
4 = 100
5 = 101
--------
XOR = 010 = 2(十进制)
由于异或和为2(非零),先手必胜。
为什么异或和能预测胜负?
证明的核心在于两个关键性质:
性质一:从异或和为零的状态出发,任何操作都会导致异或和非零。
设当前状态为 $(a_1, a_2, ..., a_n)$,异或和 $s = a_1 \oplus a_2 \oplus ... \oplus a_n = 0$。如果从第 $i$ 堆取走石子,使 $a_i$ 变为 $a_i'$,新的异或和为:
$$s' = s \oplus a_i \oplus a_i' = a_i \oplus a_i'$$由于 $a_i' < a_i$,两者必然不相等,因此 $s' \neq 0$。
性质二:从异或和非零的状态出发,总存在一种操作可以使异或和变为零。
设当前异或和为 $s \neq 0$,找到 $s$ 的最高非零位 $d$。必然存在一堆 $a_i$,其二进制表示在第 $d$ 位为1。将 $a_i$ 变为 $a_i' = a_i \oplus s$,可以证明 $a_i' < a_i$(因为高位变小了),这是一步合法操作。新的异或和为:
$$s' = s \oplus a_i \oplus a_i' = s \oplus a_i \oplus (a_i \oplus s) = 0$$这两个性质揭示了必胜策略:始终保持异或和为零,对手就无法避免地将异或和变为非零,如此往复,最终你将取走最后一颗石子。

LeetCode 292. Nim Game
这是Nim博弈最简单的变种:只有一堆石子,每次可以取1到3颗。
class Solution {
public boolean canWinNim(int n) {
// 如果石子数是4的倍数,先手必败
// 否则先手可以取走 n % 4 颗,留给对手4的倍数
return n % 4 != 0;
}
}
这道题的关键在于识别模式。当 $n \leq 3$ 时先手直接取完获胜;当 $n = 4$ 时无论取1、2、还是3颗,对手都能取完剩余石子获胜;当 $n = 5, 6, 7$ 时先手可以分别取走1、2、3颗,留给对手4颗。规律清晰:4的倍数是必败态,其他都是必胜态。
时间复杂度:$O(1)$,仅需一次取模运算。
空间复杂度:$O(1)$。
Sprague-Grundy定理:将任意游戏转化为Nim
Nim博弈的异或和策略虽然优雅,但现实中的游戏规则往往更加复杂。1935年和1939年,Roland Sprague和Patrick Michael Grundy独立证明了同一个定理,彻底改变了这一局面:任何公平组合游戏都等价于某个规模的Nim博弈。
Grundy数的定义
对于游戏中的任意状态 $v$,定义其Grundy数(也称Nimber)为:
$$G(v) = \text{mex}\{G(u) : u \text{ 是 } v \text{ 的后继状态}\}$$其中 $\text{mex}$(minimum excludant)函数返回不在集合中的最小非负整数。
Grundy数为0的状态是必败态,Grundy数大于0的状态是必胜态。这为分析任意公平组合游戏提供了统一的框架。
组合游戏的Grundy数
当游戏由多个独立的子游戏组成时(比如Nim中的多堆石子),总状态的Grundy数等于各子游戏Grundy数的异或和:
$$G(\text{total}) = G_1 \oplus G_2 \oplus ... \oplus G_n$$这就是Sprague-Grundy定理的核心结论。它告诉我们:分析复杂游戏,只需计算每个子游戏的Grundy数,然后异或起来。
计算Grundy数的示例
考虑一个游戏:有一堆 $n$ 颗石子,每次可以取走1、2或3颗。计算各状态的Grundy数:
- $G(0) = \text{mex}\{\} = 0$(没有后继状态)
- $G(1) = \text{mex}\{G(0)\} = \text{mex}\{0\} = 1$
- $G(2) = \text{mex}\{G(1), G(0)\} = \text{mex}\{1, 0\} = 2$
- $G(3) = \text{mex}\{G(2), G(1), G(0)\} = \text{mex}\{2, 1, 0\} = 3$
- $G(4) = \text{mex}\{G(3), G(2), G(1)\} = \text{mex}\{3, 2, 1\} = 0$
- $G(5) = \text{mex}\{G(4), G(3), G(2)\} = \text{mex}\{0, 3, 2\} = 1$
规律显现:$G(n) = n \mod 4$。这与LeetCode 292的结论一致。
极大极小算法:零和博弈的最优策略
并非所有博弈论问题都能用Nim理论解决。当游戏不再"公平"(双方可操作不同)时,需要更通用的框架——极大极小算法(Minimax)。
零和博弈的基本假设
极大极小算法适用于"零和博弈":一方收益等于另一方损失。在此假设下,算法模拟双方都采取最优策略的游戏过程:
- 最大化玩家(MAX):选择使评估函数最大的行动
- 最小化玩家(MIN):选择使评估函数最小的行动
算法框架
public int minimax(int depth, boolean isMaximizingPlayer) {
// 终止条件:达到最大深度或游戏结束
if (depth == 0 || isGameOver()) {
return evaluate();
}
if (isMaximizingPlayer) {
int maxEval = Integer.MIN_VALUE;
for (Move move : getAllPossibleMoves()) {
makeMove(move);
int eval = minimax(depth - 1, false);
undoMove(move);
maxEval = Math.max(maxEval, eval);
}
return maxEval;
} else {
int minEval = Integer.MAX_VALUE;
for (Move move : getAllPossibleMoves()) {
makeMove(move);
int eval = minimax(depth - 1, true);
undoMove(move);
minEval = Math.min(minEval, eval);
}
return minEval;
}
}
算法的时间复杂度为 $O(b^d)$,其中 $b$ 是分支因子(每步平均可行走法数),$d$ 是搜索深度。对于井字棋这样的简单游戏,$b \approx 5, d \approx 9$,完全可以穷举。但对于国际象棋($b \approx 35, d \approx 100$),直接使用极大极小算法不可行。
LeetCode 486. Predict the Winner
这道题是极大极小算法的经典应用:给定一个数组,两名玩家轮流从两端取数,预测先手是否能获胜。
class Solution {
public boolean PredictTheWinner(int[] nums) {
int n = nums.length;
// dp[i][j] 表示在区间 [i, j] 内先手能获得的最大分数差
int[][] dp = new int[n][n];
// 基本情况:只有一个数时,先手直接取走
for (int i = 0; i < n; i++) {
dp[i][i] = nums[i];
}
// 区间DP:从小区间扩展到大区间
for (int len = 2; len <= n; len++) {
for (int i = 0; i <= n - len; i++) {
int j = i + len - 1;
// 先手取左端:获得 nums[i],然后对手在 [i+1, j] 上先手
// 先手取右端:获得 nums[j],然后对手在 [i, j-1] 上先手
// 对手会最大化自己的收益,相当于最小化当前玩家的收益
dp[i][j] = Math.max(
nums[i] - dp[i + 1][j],
nums[j] - dp[i][j - 1]
);
}
}
// 先手能获得非负的分数差则获胜
return dp[0][n - 1] >= 0;
}
}
这里的关键洞察是:分数差可以简化极大极小的交替逻辑。当前玩家的收益减去对手的收益,本质上就是极大极小算法要优化的目标。
时间复杂度:$O(n^2)$,需要填充 $n^2$ 个状态。
空间复杂度:$O(n^2)$,可优化到 $O(n)$。
Alpha-Beta剪枝:极大极小的加速器
极大极小算法的问题在于搜索空间呈指数增长。Alpha-Beta剪枝是一种优化技术,可以在不影响结果的情况下跳过大量无需搜索的分支。
核心思想
维护两个值:
- Alpha:MAX玩家当前能保证的最小收益(下界)
- Beta:MIN玩家当前能保证的最大损失(上界)
当发现某个分支不可能比已知最优解更好时,立即停止搜索该分支。
public int alphabeta(int depth, int alpha, int beta, boolean isMaximizingPlayer) {
if (depth == 0 || isGameOver()) {
return evaluate();
}
if (isMaximizingPlayer) {
int maxEval = Integer.MIN_VALUE;
for (Move move : getAllPossibleMoves()) {
makeMove(move);
int eval = alphabeta(depth - 1, alpha, beta, false);
undoMove(move);
maxEval = Math.max(maxEval, eval);
alpha = Math.max(alpha, eval);
if (beta <= alpha) {
break; // 剪枝:MIN玩家不会选择这条路径
}
}
return maxEval;
} else {
int minEval = Integer.MAX_VALUE;
for (Move move : getAllPossibleMoves()) {
makeMove(move);
int eval = alphabeta(depth - 1, alpha, beta, true);
undoMove(move);
minEval = Math.min(minEval, eval);
beta = Math.min(beta, eval);
if (beta <= alpha) {
break; // 剪枝:MAX玩家不会选择这条路径
}
}
return minEval;
}
}

图片来源: HackerEarth - Minimax Algorithm with Alpha-Beta Pruning
剪枝原理图解
假设MAX玩家在某个节点已经找到了一个收益为3的走法。当探索另一个分支时,如果MIN玩家能强制收益降到2以下,MAX玩家就完全没有理由选择这个分支。Alpha-Beta剪枝正是利用这一原理,在搜索过程中动态剪掉不可能更优的分支。
在最优情况下,Alpha-Beta剪枝可以将时间复杂度从 $O(b^d)$ 降低到 $O(b^{d/2})$,相当于搜索深度翻倍。
Stone Game系列:博弈论与动态规划的完美结合
LeetCode上的Stone Game系列是博弈论算法的综合演练场,从简单的数学推导到复杂的状态空间搜索,涵盖了博弈论的各个层面。
LeetCode 877. Stone Game
Alice和Bob轮流从数组两端取石子,Alice先手。由于数组长度为偶数且石子总数为奇数,Alice总是必胜。
class Solution {
public boolean stoneGame(int[] piles) {
// 数学结论:先手总是必胜
// 因为Alice可以预先选择所有偶数位置或所有奇数位置
// 两种方案的总和必有一个大于对方
return true;
}
}
如果需要计算具体分数,则使用动态规划:
class Solution {
public boolean stoneGame(int[] piles) {
int n = piles.length;
int[][] dp = new int[n][n];
for (int i = 0; i < n; i++) {
dp[i][i] = piles[i];
}
for (int len = 2; len <= n; len++) {
for (int i = 0; i <= n - len; i++) {
int j = i + len - 1;
dp[i][j] = Math.max(
piles[i] - dp[i + 1][j],
piles[j] - dp[i][j - 1]
);
}
}
return dp[0][n - 1] > 0;
}
}
LeetCode 1140. Stone Game II
规则升级:每轮可以取前 $X$ 堆石子($1 \leq X \leq 2M$),然后 $M$ 更新为 $\max(M, X)$。
class Solution {
private int[] prefixSum;
private Integer[][] memo;
private int n;
public int stoneGameII(int[] piles) {
n = piles.length;
prefixSum = new int[n + 1];
memo = new Integer[n][n + 1];
for (int i = 0; i < n; i++) {
prefixSum[i + 1] = prefixSum[i] + piles[i];
}
return dfs(0, 1);
}
private int dfs(int i, int m) {
// 如果能取走所有剩余石子,直接取走
if (m * 2 >= n - i) {
return prefixSum[n] - prefixSum[i];
}
if (memo[i][m] != null) {
return memo[i][m];
}
int result = 0;
for (int x = 1; x <= 2 * m; x++) {
// 当前玩家取走前x堆,获得这些石子
// 对手在剩余石子上的最优收益通过递归计算
// 当前玩家的总收益 = 取走的石子 + 剩余石子 - 对手的收益
int currentGain = prefixSum[i + x] - prefixSum[i];
int remainingStones = prefixSum[n] - prefixSum[i + x];
int opponentGain = dfs(i + x, Math.max(m, x));
result = Math.max(result, currentGain + remainingStones - opponentGain);
}
memo[i][m] = result;
return result;
}
}
时间复杂度:$O(n^3)$,状态数为 $O(n^2)$,每个状态需要遍历 $O(n)$ 个选择。
空间复杂度:$O(n^2)$。
LeetCode 1510. Stone Game IV
给定 $n$ 颗石子,玩家轮流取走完全平方数颗石子。取走最后一颗者获胜。
class Solution {
public boolean winnerSquareGame(int n) {
// dp[i] = true 表示剩余 i 颗石子时当前玩家必胜
boolean[] dp = new boolean[n + 1];
for (int i = 1; i <= n; i++) {
// 尝试所有可能的完全平方数
for (int k = 1; k * k <= i; k++) {
// 如果存在一种取法使得对手必败,则当前玩家必胜
if (!dp[i - k * k]) {
dp[i] = true;
break;
}
}
}
return dp[n];
}
}
这道题的本质是计算每个状态的Grundy数,只是状态转移更简单——Grundy数大于0就是必胜态。
时间复杂度:$O(n\sqrt{n})$。
空间复杂度:$O(n)$。
博弈论算法的统一框架
回顾上述所有问题,可以总结出一个统一的解题框架:
类型一:数学推导型
当游戏规则简单且有明显的数学规律时,直接推导公式:
- Nim博弈:异或和判断
- Stone Game I:偶数长度保证先手必胜
类型二:Grundy数型
当游戏可以分解为独立子游戏时:
- 计算每个子游戏的Grundy数
- 异或求和判断必胜/必败态
- 必胜时构造必胜策略
类型三:极大极小型
当游戏双方操作不对称或状态空间复杂时:
- 定义状态和评估函数
- 使用DP或记忆化搜索计算每个状态的值
- 当前玩家选择最大化自身收益的操作
LeetCode博弈论题目汇总
| 题号 | 题目名称 | 核心算法 | 难度 |
|---|---|---|---|
| 292 | Nim Game | 数学推导 | Easy |
| 877 | Stone Game | 数学/DP | Medium |
| 486 | Predict the Winner | 极大极小DP | Medium |
| 1025 | Divisor Game | 数学推导 | Easy |
| 1140 | Stone Game II | 记忆化搜索 | Medium |
| 1406 | Stone Game III | DP | Hard |
| 1510 | Stone Game IV | Grundy数DP | Hard |
| 1908 | Game of Nim | Nim理论 | Medium |
| 913 | Cat and Mouse | 极大极小/状态压缩 | Hard |
从理论到实践:面试中的博弈论
博弈论题目在面试中频繁出现,但大多数都可以用以下模式解决:
第一步:判断游戏类型
是否为公平组合游戏?双方操作是否对称?游戏是否有限?
第二步:寻找必胜/必败态规律
从小规模数据开始,尝试发现必胜态和必败态的分布规律。
第三步:选择合适的算法
- 规律明显 → 数学公式
- 可分解 → Grundy数
- 不对称/复杂 → 极大极小 + DP
第四步:验证边界情况
确保正确处理游戏结束状态、单元素状态等边界条件。
博弈论的魅力在于:看似复杂的多方对抗,往往可以用简洁的数学公式完全刻画。从Nim博弈的异或和,到极大极小算法的递归搜索,再到Alpha-Beta剪枝的优化,这套思维框架不仅适用于编程面试,更是理解人工智能、经济学、政治学等领域的基石。
参考资料
- Bouton, C. L. (1901). “Nim, A Game with a Complete Mathematical Theory”. Annals of Mathematics.
- CP-Algorithms. “Sprague-Grundy theorem. Nim”. https://cp-algorithms.com/game_theory/sprague-grundy-nim.html
- GeeksforGeeks. “Minimax Algorithm in Game Theory”. https://www.geeksforgeeks.org/minimax-algorithm-in-game-theory/
- Plus Magazine. “Play to win with Nim”. https://plus.maths.org/play-win-nim
- LeetCode. “Game Theory Problem List”. https://leetcode.com/problem-list/game-theory/
- USACO Guide. “Game Theory”. https://usaco.guide/adv/game-theory
- HackerEarth. “Minimax Algorithm with Alpha-Beta Pruning”. https://www.hackerearth.com/blog/minimax-algorithm-alpha-beta-pruning