题目描述
思路解析动画文字版
先用 DP 把胜负态推出来,再观察奇偶规律,比直接背 n%2 更可靠。
从 n=1 这个必败态开始,奇偶胜负会交替固定下来。
dp[1] = False:没有合法操作:表格每一列就是一个 dp[i],这一行的布尔值表示“当前先手玩家面对数字 i 能不能赢”。i=1 时不存在任何满足 0 < x < 1 的整数,所以当前玩家无法操作,dp[1]=false。
dp[2] = True:能走到 dp[1]=False:只要能把对手送到一个必败态,当前 i 就是必胜态。
dp[3] = False:唯一走法送到 dp[2]=True:3 只能把 2 交给对手,而 2 是对手的必胜态,所以 dp[3]=False。
dp[4] = True:选 x=1 走到 dp[3]=False:DP 里一旦找到能让对手输的 x,就可以 break。
dp[5] = False:奇数只能送到偶数:5 是奇数,合法真因子是奇数;奇数减奇数会变偶数,也就是把必胜态交给对手。
dp[6] = True:选 x=1 走到 dp[5]=False:偶数不需要找复杂因子,选 x=1 就能把奇数必败态留给对手。
规律固定:奇数输,偶数赢:从 1 输开始,奇数全是必败态,偶数全是必胜态。
代码收束:判断 n 是否为偶数:DP 表推到这里已经能看出奇偶规律,优化版代码可以直接写 return n % 2 == 0。
代码循环如何对应 DP 表:DP 表不是只展示结果:每个 True 都来自代码里找到某个 x,使得下一步 i-x 是对手必败态。
归纳证明:为什么规律能延伸到任意 n:这不是只观察了 1 到 6,而是由奇偶变化的归纳规则推出所有 n 的胜负。
这题可以用 DP 验证,但上线代码用数学规律更直接。
参考代码
class Solution: def divisorGame(self, n): dp = [False] * (n + 1) for i in range(2, n + 1): for x in range(1, i): if i % x == 0 and not dp[i - x]: dp[i] = True break return dp[n]复杂度
- 时间复杂度:O(n²),DP 推导版枚举 i 和候选因子 x;n <= 1000,可以通过。规律优化版是 O(1)。
- 空间复杂度:O(n),DP 推导版保存胜负表;规律优化版是 O(1)。
易错点
这道题到这就讲完了。动画和文字是同一套思路——别光看,关掉页面自己默写一遍。然后顺着主线继续:
泰波那契数
LeetCode 1137 · 简单 · 沿着 动态规划套路 继续往下推进
把这道题真正学会,再走
图解算法年卡 ¥99 /年
- ✓本题每步动画的吴师兄语音讲解(全站陆续覆盖)
- ✓小欧带 8 步通关训练——追问到不看答案也能写对、能 30 秒讲给面试官听
- ✓学习报告,记录每道题的掌握程度
76k+ GitHub Star · 吴师兄开源图解算法,几十万开发者在看的算法讲解
想成体系刷透这类套路?去图解算法专题