难度: Medium
Submission
运行时间: 31 ms
内存: 16.1 MB
class Solution: def canWin(self, s: str) -> bool: def mex(vals): i = 0 while i in vals: i += 1 return i @cache def dfs(x): if x < 2: return 0 vals = set() for a in range(x-1): b = x-a-2 vals.add(dfs(a) ^ dfs(b)) return mex(vals) plus = [] l, r = 0, 0 while r < len(s): if s[r] == '-': d = r-l if d >= 2: plus.append(d) l = r + 1 r += 1 d = len(s) - l if d >= 2: plus.append(d) v = 0 for x in plus: v^= dfs(x) return v > 0 # 作者:Firepaw # 链接:https://leetcode.cn/problems/flip-game-ii/solutions/58448/game-theory-100-python3-by-firepaw/ # 来源:力扣(LeetCode) # 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
Explain
这个题解使用了博弈论中的 SG 定理。将初始的字符串 s 按照连续的 '+' 分割成多个子串,每个子串代表一个独立的游戏局面。对每个子串计算它的 SG 函数值,最后将所有 SG 函数值做异或操作,如果结果大于 0,则先手必胜,否则先手必败。 在计算单个子串的 SG 函数时,使用记忆化搜索 dfs(x),其中 x 表示当前子串的长度。搜索过程中,枚举所有可能的分割方式,将分割后的两部分递归计算 SG 函数,并做异或操作,得到一个 SG 函数值的集合。最后,用 mex 函数找到最小的不属于这个集合的非负整数,作为当前状态的 SG 函数值。
时间复杂度: O(n^3)
空间复杂度: O(n)
```python class Solution: def canWin(self, s: str) -> bool: def mex(vals): i = 0 while i in vals: i += 1 return i @cache def dfs(x): if x < 2: return 0 vals = set() for a in range(x-1): b = x-a-2 vals.add(dfs(a) ^ dfs(b)) return mex(vals) plus = [] l, r = 0, 0 while r < len(s): if s[r] == '-': d = r-l if d >= 2: plus.append(d) l = r + 1 r += 1 d = len(s) - l if d >= 2: plus.append(d) v = 0 for x in plus: v^= dfs(x) return v > 0 ``` 注释: 1. `mex` 函数用于计算 mex 值,即找到最小的不属于给定集合的非负整数。 2. `@cache` 装饰器用于记忆化搜索,避免重复计算。 3. `dfs(x)` 函数用于计算当前状态的 SG 函数值,其中 `x` 表示当前子串的长度。 4. 首先将初始字符串按照连续的 '+' 分割成多个子串,存储在 `plus` 数组中。 5. 对于每个子串,调用 `dfs(x)` 计算其 SG 函数值,并将所有 SG 函数值做异或操作,得到最终结果 `v`。 6. 如果 `v > 0`,则先手必胜,否则先手必败。
Explore
SG定理(Sprague-Grundy定理)是博弈论中的一个重要概念,用于解决可以分解为多个独立子游戏的组合游戏。基本原理是每个可分解的游戏状态都可以分配一个名为SG函数的值。SG函数的值是基于该状态下所有可能移动后的状态的SG值计算得出的。具体来说,SG函数值是这些后续状态的SG值所不能达到的最小非负整数(mex)。在翻转游戏中,每个连续的 '+' 子串被视为一个独立的游戏状态。SG定理应用于此游戏的方式是,如果从初始状态开始,所有独立子游戏的SG值的异或和非零,则先手有必胜策略;如果异或和为零,则先手必败。
在翻转游戏中,玩家需要翻转连续的两个 '+' 符号为 '-' 符号。因此,对于长度小于2的子串,即不包含连续两个 '+' 的子串,玩家无法执行任何有效的移动。因此,这样的子串被视为终止状态,其SG函数值为0。这表示该游戏状态是一个P位置(先手必败),因为先手玩家无法进行任何改变游戏结果的操作。
`mex`函数(Minimum EXcludant)的作用是找到一个集合中未出现的最小非负整数。工作原理是:首先从0开始逐个检查整数是否在集合中。这个过程是通过一个循环实现的,循环的索引从0开始,如果当前数字存在于集合中,则索引递增继续检查下一个数字;如果不存在,则这个数字就是mex值。这种方法保证了找到的是最小的缺失非负整数,因为它是按顺序从最小值开始检查的。
在翻转游戏中,玩家的操作是寻找连续的 '+' 并将其翻转为 '-'. 这意味着只有连续的 '+' 子串是可以被操作的独立单元。通过将字符串按照 '+' 的连续段进行分割,我们实际上是在定义游戏的独立子局面,每个子局面内部只包含连续的 '+'. 这种分割确实考虑了所有可能的翻转操作,因为任何有效的翻转操作都只能在至少两个连续 '+' 的子串内进行。这种方法有效地简化了问题,将复杂的字符串分解为一系列独立解决的小问题。