如何加速递归算法

时间:2016-08-28 12:01:11

标签: python algorithm

我试图解决Hackerrank挑战Game of Stones,其中一个(缩短的)问题陈述将在下面复制。

enter image description here

我提出了以下解决方案:

# 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中,由于超时而终止:

enter image description here

确实,我注意到评估which_player_wins(40)已经需要约2秒。对于更快解决方案的任何想法都不会超时?

3 个答案:

答案 0 :(得分:7)

从问题描述中可以看出,您可以保留每个计算的中间结果和最终结果,以便在以后的计算中使用。如果是这样,则递归算法不是最佳的。相反,使用动态编程风格。

换句话说,保留一个全局数组,用于存储之前的胜负确定结果。当您确定新值n时,不要一直递归到最后,请使用先前的确定。

例如,当你到达n=10时,你会看到第一个移除3块石头的玩家留下了7块石头,你已经看到它是第二个玩家的胜利。因此,10个宝石对于第一个玩家来说是一个胜利。

我相信你的递归算法会重新计算n=7的结果,而不是使用以前的工作。

答案 1 :(得分:2)

如果您有无限的递归级别,那么您的解决方案将会起作用(尽管速度很慢)。但是,由于您没有重新使用已经计算的结果,因此您很快就会达到Python的递归限制。如上所述,一个好的解决方案是使用非递归算法重写代码。

或者,你可以保留你拥有的代码,但让它重复使用你以前见过的任何结果。这称为memoization,一种简单的实现方法是将memoization装饰器添加到计算下一步的代码部分。这是一种使用memoization加速递归算法的方法,我认为这是你特别要求的:

  1. 将第一个else之后的所有代码转换为返回“First”或“Second”的函数next_move(n)

  2. 添加memoize(f)函数,该函数将next_move(n),如果已经计算了n的结果,则避免递归调用。

  3. @memoize定义之前添加装饰线next_move

  4. 结果代码:

    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)

这导致成功解决了挑战(见下文)。

enter image description here