
1. 问题背景与理解石子游戏II是LeetCode上一道经典的动态规划题目编号1140。这道题属于博弈类动态规划的典型代表考察玩家在面对游戏规则时的最优决策能力。题目描述两位玩家爱丽丝和鲍勃轮流从石子堆中取石子每次可以取走前X堆1 ≤ X ≤ 2M然后M变为max(M, X)。游戏的目标是获得最多的石子。这类博弈问题在实际中有诸多应用场景比如资源分配、投资决策等。理解这类问题的解法不仅能帮助我们提升算法能力更能培养面对复杂决策时的系统性思维。2. 问题分析与解法思路2.1 游戏规则解析游戏规则有几个关键点需要注意玩家每次可以取走前1到2M堆石子取走X堆后M会更新为max(M, X)两位玩家都采取最优策略爱丽丝先手这些规则决定了我们需要考虑当前剩余石子堆的状态当前M值当前玩家的选择对后续游戏的影响2.2 动态规划状态定义基于上述分析我们可以定义dp[i][m]表示当剩下第i堆到最后一堆石子时当前M值为m当前玩家能获得的最大石子数这个状态定义涵盖了游戏的所有关键信息为后续的状态转移奠定了基础。2.3 状态转移方程状态转移需要考虑当前玩家所有可能的取法1到2m堆并选择对自己最有利的方案dp[i][m] max( sum(piles[i:ix]) (sum(piles[ix:]) - dp[ix][max(m,x)]) for x in range(1, 2*m1) )其中sum(piles[i:ix])是当前玩家这次取走的石子数sum(piles[ix:]) - dp[ix][max(m,x)]是当前玩家在对手采取最优策略后能获得的剩余石子数3. 具体实现与优化3.1 基础实现首先我们可以实现一个记忆化搜索的版本def stoneGameII(piles): n len(piles) prefix [0]*(n1) for i in range(n-1, -1, -1): prefix[i] prefix[i1] piles[i] from functools import lru_cache lru_cache(maxsizeNone) def dp(i, m): if i 2*m n: return prefix[i] max_stones 0 for x in range(1, 2*m1): next_m max(m, x) current prefix[i] - prefix[ix] remaining prefix[ix] - dp(ix, next_m) total current remaining if total max_stones: max_stones total return max_stones return dp(0, 1)3.2 优化技巧前缀和优化预先计算前缀和数组可以快速计算任意区间的和记忆化搜索使用lru_cache避免重复计算边界条件处理当剩余石子堆数≤2m时可以直接取走所有石子3.3 时间复杂度分析状态总数O(n^2)i从0到n-1m从1到n每个状态的转移O(n)x最多取n次总时间复杂度O(n^3)空间复杂度O(n^2)4. 常见问题与调试技巧4.1 常见错误状态定义不完整忘记考虑M的变化边界条件处理不当特别是当ix超过数组长度时石子数计算错误没有使用前缀和导致计算效率低下4.2 调试建议从小规模测试用例开始验证打印中间状态帮助理解对比暴力解法的结果4.3 测试用例设计好的测试用例应该包括最小规模1-2堆石子M变化较大的情况石子数分布不均匀的情况边界条件如所有石子数相同例如[2,7,9,4,4] → 10 [1,2,3,4,5,100] → 104 [1] → 1 [1,1,1,1] → 25. 算法扩展与变种5.1 类似题目Stone Game I (LeetCode 877)Stone Game III (LeetCode 1406)Stone Game IV (LeetCode 1510)5.2 变种思考如果每次可以取1到k*M堆石子k为常数如果M的更新规则不同如果石子堆成环形排列5.3 实际应用这类博弈动态规划问题可以应用于资源分配决策投资策略优化游戏AI设计6. 个人解题心得在实际解决这个问题时我发现有几个关键点特别重要明确状态定义必须包含所有影响决策的关键信息理解对手的最优策略这是博弈问题的核心从简单情况入手先考虑边界条件和小规模情况优化计算效率前缀和和记忆化是必不可少的工具这道题教会我们面对复杂决策问题时系统地分解问题、定义状态、考虑对手反应是找到最优解的关键。这种思维方式不仅在算法竞赛中有用在实际生活和工作中也同样适用。