T1510.石子游戏 IV
game theory, dp, https://leetcode.cn/problems/stone-game-iv/
Alice 和 Bob 两个人轮流玩一个游戏,Alice 先手。
一开始,有 n 个石子堆在一起。每个人轮流操作,正在操作的玩家可以从石子堆里拿走 任意 非零 平方数 个石子。
如果石子堆里没有石子了,则无法操作的玩家输掉游戏。
给你正整数 n ,且已知两个人都采取最优策略。如果 Alice 会赢得比赛,那么返回 True ,否则返回 False 。
示例 1:
输入:n = 1
输出:true
解释:Alice 拿走 1 个石子并赢得胜利,因为 Bob 无法进行任何操作。示例 2:
输入:n = 2
输出:false
解释:Alice 只能拿走 1 个石子,然后 Bob 拿走最后一个石子并赢得胜利(2 -> 1 -> 0)。示例 3:
输入:n = 4
输出:true
解释:n 已经是一个平方数,Alice 可以一次全拿掉 4 个石子并赢得胜利(4 -> 0)。示例 4:
输入:n = 7
输出:false
解释:当 Bob 采取最优策略时,Alice 无法赢得比赛。
如果 Alice 一开始拿走 4 个石子, Bob 会拿走 1 个石子,然后 Alice 只能拿走 1 个石子,Bob 拿走最后一个石子并赢得胜利(7 -> 3 -> 2 -> 1 -> 0)。
如果 Alice 一开始拿走 1 个石子, Bob 会拿走 4 个石子,然后 Alice 只能拿走 1 个石子,Bob 拿走最后一个石子并赢得胜利(7 -> 6 -> 2 -> 1 -> 0)。示例 5:
输入:n = 17
输出:false
解释:如果 Bob 采取最优策略,Alice 无法赢得胜利。提示:
1 <= n <= 10^5
这道题是一个典型的博弈论 + 动态规划(Dynamic Programming)问题。
解题思路
定义状态: 我们可以定义一个布尔型数组
dp,其中dp[i]表示当面对剩下i个石子时,当前玩家(轮到操作的人)是否必胜。dp[i] = True:表示当前玩家必胜。dp[i] = False:表示当前玩家必败。
基本情况(Base Case):
dp[0] = False:如果没有石子了,当前玩家无法操作,直接输掉。
状态转移方程: 对于剩下的
i个石子,当前玩家可以拿走个石子(其中 且 )。 拿走 个石子后,石子数量变成 i - k^2,轮到对手操作。- 如果对手在
i - k^2的状态下是必败的(即dp[i - k*k] == False),那么当前玩家只要选择拿走个石子,就能把必败的状态留给对手,从而让自己获得胜利! - 因此,只要存在任意一个
,满足 dp[i - k*k] == False,那么dp[i]就可以设为True,并且可以直接停止继续寻找(因为已经找到了获胜策略)。 - 如果尝试了所有可能的
,发现对手在所有 i - k*k状态下都是必胜的(即dp[i - k*k] == True),那么当前玩家无论怎么选都会输,所以dp[i] = False。
- 如果对手在
Python 代码实现
python
class Solution:
def winnerSquareGame(self, n: int) -> bool:
# dp[i] 表示面对 i 个石子时,当前操作的人是否必胜
dp = [False] * (n + 1)
# 从 1 个石子推导到 n 个石子
for i in range(1, n + 1):
k = 1
while k * k <= i:
# 如果拿走 k*k 个石子后,对手处于必败状态
if not dp[i - k * k]:
dp[i] = True
break # 只要找到一种必胜策略,即可停止尝试其他的平方数
k += 1
return dp[n]复杂度分析
时间复杂度:
- 外层循环运行
次。 - 内层循环对于每个
,最多运行 次。 - 总时间复杂度为
。当 时,计算次数约为 次,可以在 Python 中轻松通过(限时通常为 1 秒)。
- 外层循环运行
空间复杂度:
- 需要一个长度为
的布尔数组 dp保存状态。
- 需要一个长度为