1025 - Divisor Game
第一种解法
可以直接考虑这道题是否存在必胜策略:首先考虑最终失败的情况必定是谁的数字变为1谁就失败,不可能是其他情况。
再由题目我们可以思考:
- 如果输入是一个偶数,那么减掉一个比它小且能整除它的正数,最后得到的可以是一个偶数(减掉的数不为1)也可以是一个奇数(减掉的数是1)
- 如果输入是一个奇数,那么减掉一个比它小且能整除它的正数,最后得到的一定是一个偶数
通过这一发现,我们可以得到一个结论:在题目已经保证了每一步的选择都是最优的前提下,我只要保证我的对手手里的数字始终是一个奇数,我就必赢。因为奇数无论怎么操作最后给我的都是偶数,而偶数是一定不会输的(只有数字是1时才会输,1是奇数),我只要每次在我拿到的偶数上减1,再把它给我的对手,我的对手手里就始终都是一个奇数,这也保证了我手里始终会是一个偶数,直到我的对手手里的数字变成1,游戏结束。
所以在这种情况下,只要输入是偶数,Alice是必胜的。
class Solution(object):
def divisorGame(self, N):
"""
:type N: int
:rtype: bool
"""
if N % 2 == 0:
return True
else:
return False
if __name__ == '__main__':
solution = Solution()
print(solution.divisorGame(4))
第二种解法
第二种解法是采用动态规划的方法。
当当前的数字为n时,状态转移的方式为:
- 初始化: d p [ 0 ] = T r u e , d p [ 1 ] = F a l s e dp[0]=True, dp[1]=False dp[0]=True,dp[1]=False。(因为当 i = 1 i=1 i=1时要考虑到 n − n / / i = 0 n-n//i=0 n−n//i=0,为了使这个值不会对结果产生影响,应该将 d p [ 0 ] dp[0] dp[0]赋值为True)
- 遍历所有的满足条件 0 < i < n , n % i = 0 0<i<n, \quad n\%i=0 0<i<n,n%i=0的 i i i,当其中有任意一种 d p [ n − i ] = F a l s e dp[n-i]=False dp[n−i]=False,则 d p [ n ] = T r u e dp[n]=True dp[n]=True。(因为题目已经要求玩家的每一步都是做出的最优操作,所以只要有至少一种能让Bob失败的方案,则Alice必胜)
- 如果上一步中找不到任何一种使Bob失败的方案,则Alice必败, d p [ n ] = F a l s e dp[n]=False dp[n]=False
import numpy as np
class Solution(object):
def divisorGame(self, N):
"""
:type N: int
:rtype: bool
"""
# 可变类型的值在子函数里面改变,主函数中也会改变
dp = [-1]*1000
dp[0] = True
dp[1] = False
return self.is_alice_win(N, dp)
def is_alice_win(self, n, dp):
# 终止条件,如果到了某个值时dp值为True或者False
if dp[n] == True or dp[n] == False:
return dp[n]
# 循环体,因为题意要求了每次都做的是最优选择,所以只要下面考虑的情况有一次是False,那么说明对方必败,而Alice必胜
for i in range(1, int(np.sqrt(n))+1): # 结束条件是n的开方的整数部
if n % i == 0: # 如果能整除
## 当i=1时,n-n//i=0,为了不对结果产生影响,
if (not self.is_alice_win(n-i, dp)) or (not self.is_alice_win(n-n//i, dp)):
dp[n] = True
# 如果所有情况都是Bob必胜,那么当前Alice必败
if dp[n] == -1:
dp[n] = False
return dp[n]
if __name__ == '__main__':
solution = Solution()
print(solution.divisorGame(555))