「这是我参与2022首次更文挑战的第6天,活动详情查看:2022首次更文挑战」
描述
Alice and Bob take turns playing a game, with Alice starting first.
Initially, there are n stones in a pile. On each player's turn, that player makes a move consisting of removing any non-zero square number of stones in the pile.
Also, if a player cannot make a move, he/she loses the game.
Given a positive integer n, return true if and only if Alice wins the game otherwise return false, assuming both players play optimally.
Example 1:
Input: n = 1
Output: true
Explanation: Alice can remove 1 stone winning the game because Bob doesn't have any moves.
Example 2:
Input: n = 2
Output: false
Explanation: Alice can only remove 1 stone, after that Bob removes the last one winning the game (2 -> 1 -> 0).
Example 3:
Input: n = 4
Output: true
Explanation: n is already a perfect square, Alice can win with one move, removing 4 stones (4 -> 0).
Note:
1 <= n <= 10^5
解析
根据题意,Alice 和 Bob 轮流玩游戏,Alice 先开始。最初的一堆石头中有 n 块。 在每个玩家的回合中,该玩家采取行动,包括移除堆中任何非零平方数的石子。如果玩家无法进行操作,将输掉比赛。现在给定一个正整数 n ,当且仅当 Alice 赢得游戏时返回 true ,否则返回 false 。
首先读完这道题我就有一个疑问,为什么最后必须是 Alice 赢了才返回 True ,漂亮国不是人权平等,怎么出这种题呢?难道 Bob 是黑人...而且我看了该题的 Hide Hint ,其中给出的解题办法就是,如果 Alice 可以迫使 Bob 进入失败状态,Alice 就可以获胜。这明摆着就是在玩 Bob ,Alice 想和人家玩还特意把人家往输整,格局太小了。
言归正传,这种题一看就知道能用 DFS 来解答,我们其实通过几个例子就能看出来,只要最后轮到 Alice 的时候剩下的石子个数是非零平方数,就说明 Alice 能一次都拿走,直接让 Bob 输掉比赛,所以我们设计 DFS 的时候就是在当前剩余石子的数量 remain 的情况下,遍历可以允许操作的非零平方数 i ,如果有一个 dfs(remain-i*i) 为 False ,那么说明在当前玩家拿走 i*i 个石子, 对方在剩下的 remain-i*i 个石子中无论怎么操作都必输,也就是说明当前玩家必赢,直接返回 True ,如果遍历完所有的之后直接返回 False ,表示当前玩家没有任何的操作方案来取得胜利。
需要注意的时候,我们这里用到了 lur_cache 装饰器来保证不会超时,因为限制条件为 n 最大是 10^5 ,所以基本上要实现 O(N) 左右的时间复杂度的代码。本思路中因为调用一次 DFS 的时间复杂度为 O(N**0.5) ,而 DFS 里面调用 DFS 的时间复杂度为 O(N),所以总共有 O(N*N**0.5) 但是因为用到了 lur_cache 装饰器,所以很多的中间过程的时间复杂度都是 O(1) ,实际上是大大减少了时间复杂度,尽管耗时多但刚好勉强能 AC 。空间复杂度为 O(N) ,因为装饰器保存了所有出现过的 DFS 函数的结果。
解答
class Solution:
def winnerSquareGame(self, n: int) -> bool:
@lru_cache(maxsize=None)
def dfs(remain):
if remain == 0:
return False
sqrt = int(remain**0.5)
for i in range(1, sqrt+1):
if not dfs(remain - i*i):
return True
return False
return dfs(n)
运行结果
Runtime: 1996 ms, faster than 42.52% of Python3 online submissions for Stone Game IV.
Memory Usage: 171.6 MB, less than 5.39% of Python3 online submissions for Stone Game IV.
解析
其实如果会上面的记忆化的 DFS 解题方法,那么自然会动态规划,使用动态规划就不需要装饰器,因为动态规划的本身 DP 数组会存储结果,我们定义 dp[i] 为表示还有 i 个石头的时候当前玩家的比赛结果,dp[i] 为 True 表示当有 i 个石头的时候当前玩家肯定会赢,否则表示肯定会输。初始化的时候我们定义一个 N+1 长度的全都是 False 的 dp 列表。最后返回 dp[n] 即为我们的答案。
我们遍历 1 到 n 的所有可能出现的石头堆的大小 i ,然后再遍历 1 到 n**0.5 的数字表示当前玩家可操作的非零平方数 j ,只要出现 dp[i-j*j] 为 False 说明对方肯定会输,那就是间接说明当前玩家肯定会赢,此时我们将 dp[i] 置为 True ,因此时石头堆大小为 i 的结果已经找到,直接 break 当前循环,再进行石头堆大小为 i+1 的结果的计算即可。
因为是两层循环,一层时间复杂度为 O(N) ,第二层的时间复杂度为 O(N**0.5) ,所以时间复杂度为 O(N*N**0.5) ,空间复杂度为 O(N) 。
解答
class Solution:
def winnerSquareGame(self, n: int) -> bool:
dp = [False] * (n + 1)
for i in range(1, n + 1):
if dp[i]:
continue
for j in range(1, int(i ** 0.5) + 1):
if not dp[i - j * j]:
dp[i] = True
break
return dp[n]
运行结果
Runtime: 811 ms, faster than 76.05% of Python3 online submissions for Stone Game IV.
Memory Usage: 14.7 MB, less than 92.22% of Python3 online submissions for Stone Game IV.
原题链接
您的支持是我最大的动力