我试图解决Hackerrank挑战Game of Stones,其中一个(缩短的)问题陈述将在下面复制。
我提出了以下解决方案:
# The lines below are for the Hackerrank submission
# T = int(raw_input().strip())
# ns = [int(raw_input().strip()) for _ in range(T)]
T = 8
ns = [1, 2, 3, 4, 5, 6, 7, 10]
legal_moves = [2, 3, 5]
def which_player_wins(n):
if n <= 1:
return "Second" # First player loses
elif n in legal_moves:
return "First" # First player wins immediately
else:
next_ns = map(lambda x: n - x, legal_moves)
next_ns = filter(lambda x: x >= 0, next_ns)
next_n_rewards = map(which_player_wins, next_ns) # Reward for opponent
if any(map(lambda x: x=="Second", next_n_rewards)): # Opponent enters a losing position
return "First"
else:
return "Second"
for n in ns:
print which_player_wins(n)
该算法本质上是minimax算法,因为它看起来向前移动然后递归调用相同的函数。问题是在Hackerrank中,由于超时而终止:
确实,我注意到评估which_player_wins(40)
已经需要约2秒。对于更快解决方案的任何想法都不会超时?
答案 0 :(得分:7)
从问题描述中可以看出,您可以保留每个计算的中间结果和最终结果,以便在以后的计算中使用。如果是这样,则递归算法不是最佳的。相反,使用动态编程风格。
换句话说,保留一个全局数组,用于存储之前的胜负确定结果。当您确定新值n
时,不要一直递归到最后,请使用先前的确定。
例如,当你到达n=10
时,你会看到第一个移除3块石头的玩家留下了7块石头,你已经看到它是第二个玩家的胜利。因此,10个宝石对于第一个玩家来说是一个胜利。
我相信你的递归算法会重新计算n=7
的结果,而不是使用以前的工作。
答案 1 :(得分:2)
如果您有无限的递归级别,那么您的解决方案将会起作用(尽管速度很慢)。但是,由于您没有重新使用已经计算的结果,因此您很快就会达到Python的递归限制。如上所述,一个好的解决方案是使用非递归算法重写代码。
或者,你可以保留你拥有的代码,但让它重复使用你以前见过的任何结果。这称为memoization,一种简单的实现方法是将memoization装饰器添加到计算下一步的代码部分。这是一种使用memoization加速递归算法的方法,我认为这是你特别要求的:
将第一个else
之后的所有代码转换为返回“First”或“Second”的函数next_move(n)
。
添加memoize(f)
函数,该函数将next_move(n)
,如果已经计算了n的结果,则避免递归调用。
在@memoize
定义之前添加装饰线next_move
。
结果代码:
T = 8
ns = [1, 2, 3, 4, 5, 6, 7, 10]
legal_moves = [2, 3, 5]
def memoize(f):
memo = {}
def helper(x):
if x not in memo:
memo[x] = f(x)
return memo[x]
return helper
@memoize
def next_move(n):
next_ns = map(lambda x: n - x, legal_moves)
next_ns = filter(lambda x: x >= 0, next_ns)
next_n_rewards = map(which_player_wins, next_ns) # Reward for opponent
if any(map(lambda x: x == "Second", next_n_rewards)): # Opponent enters a losing position
return "First"
else:
return "Second"
def which_player_wins(n):
if n <= 1:
return "Second" # First player loses
elif n in legal_moves:
return "First" # First player wins immediately
else:
return next_move(n)
for n in ns:
print which_player_wins(n)
这极大地加快了计算速度并降低了所需的递归水平。在我的计算机上,n = 100在0.8毫秒内解决。
答案 2 :(得分:0)
按照Rory Daulton的建议使用动态编程,我重写了which_player_wins
方法,如下所示:
# The lines below are for the Hackerrank submission
# T = int(raw_input().strip())
# ns = [int(raw_input().strip()) for _ in range(T)]
T = 8
ns = [1, 2, 3, 4, 5, 6, 7, 10]
def which_player_wins(n):
moves = [2, 3, 5]
table = {j:"" for j in range(n+1)}
table[0] = "Second" # If it is the first player's turn an no stones are on the table, the second player wins
table[1] = "Second" # No legal moves are available with only one stone left on the board
for i in range(2,n+1):
next_n = [i - move for move in moves if i - move >= 0]
next_n_results = [table[k] for k in next_n]
if any([result == "Second" for result in next_n_results]):
table[i] = "First"
else:
table[i] = "Second"
return table[n]
for n in ns:
print which_player_wins(n)
这导致成功解决了挑战(见下文)。