一、题目描述

一个环,有n个点(编号 0 ~ n-1 ), 问从0点出发,经过k步回到原点(0点)有多少种方法 ?

二、解题思路 & 代码

再回到 0 点可以从右面回来,也可以从左面回来,即先到达旁边的一个点,看看有多少回来的方法即可。所以运用动态规划的思想,我们可以写出递推式如下:

d p ( k , j ) = d p ( k − 1 , j − 1 ) + d p ( k − 1 , j + 1 ) dp(k, j) = dp(k-1, j-1) + dp(k-1, j+1) dp(k,j)=dp(k1,j1)+dp(k1,j+1)

d p ( k , j ) dp(k, j) dp(k,j) 表示从点 j j j k k k 步到达原点 0 0 0 的方法数

因此可以转化为他相邻的点经过 k − 1 k-1 k1 步回到原点的问题,点 j j j 相邻的点即 j − 1 j - 1 j1 j + 1 j + 1 j+1。 这样将问题的规模缩小。

由于是环的问题, j − 1 j-1 j1, j + 1 j+1 j+1 可能会超出 0 0 0 n − 1 n-1 n1 的范围,因此,我们将递推式改成如下:

d p ( k , j ) = d p ( k − 1 , ( j − 1 + n ) % n ) + d p ( k − 1 , ( j + 1 ) % n ) dp(k, j)=dp(k-1,(j-1+n) \% n)+dp(k-1,(j+1) \% n) dp(k,j)=dp(k1,(j1+n)%n)+dp(k1,(j+1)%n)

因为问题从走 k k k 步转化为走 k − 1 k-1 k1 步的问题,所以在写程序的时候我们就按照 k k k 0 0 0 开始递增的循环写,这样当计算第 k k k 步的时候可以直接使用 k − 1 k-1 k1 步的结果。

def get_step_num_1(n, k):
    if (n == 0 or n == 1):
        return 1
    # 如果只有n == 2,则偶数有1个方法,奇数不能到达
    if (n == 2):
        if (k % 2 == 0):
            return 1
        else:
            return 0

    dp = [[0] * n for i in range(k + 1)]  # 空间复杂度 O(n * k)
    
    dp[0][0] = 1
    for i in range(1, n):
        dp[0][i] = 0

    # j is the current step
    for j in range(1, k + 1):
        for i in range(0, n):
            dp[j][i] = dp[j - 1][(i - 1 + n) % n] + dp[j - 1][(i + 1) % n]

    # 这里的0对应的是回到0点,达到任意点可以通过将0改为目标点即可
    return dp[k][0]


if __name__ == '__main__':
    n = 102
    k = 12
    res1 = get_step_num_1(n, k)
    res2 = get_step_num_2(n, k)
    print(res1)
    print(res2)
  • 空间复杂度 O ( k ∗ n ) O(k * n) O(kn)

扩展:

如果问到到达任意点的方法数,则将结果的 d p [ k ] [ 0 ] dp[k][0] dp[k][0] 改为对应的目标值即可,比如达到 5 5 5 号的方法数,则 return dp[k][5]

优化:

因为这里的 第一维 j j j 只和前一个状态 j − 1 j - 1 j1 有关,所以还可以优化一下:

def get_step_num_2(n,k):
    if (n == 0 or n == 1):
        return 1
    # 如果只有n == 2,则偶数有1个方法,奇数不能到达
    if (n == 2):
        if (k % 2 == 0):
            return 1
        else:
            return 0

    dp = [[0] * n for i in range(2)]  # 空间复杂度 O(n * 2)

    flag = 1

    dp[0][0] = 1
    for i in range(1, n):
        dp[0][i] = 0

    # j is the current step
    for j in range(1, k + 1):
        for i in range(0, n):
            dp[flag][i] = dp[int(not flag)][(i-1+n) % n] + dp[int(not flag)][(i+1) % n]
        flag = int(not flag)

    # 这里的0对应的是回到0点,达到任意点可以通过将0改为目标点即可
    return dp[int(not flag)][0]
  • 空间复杂度 O ( 2 ∗ n ) O(2 * n) O(2n)

参考:

  1. 一个环,有n个点, 问从0点出发,经过k步回到原点有多少种方法(字节面试题,java解法)
  2. 一个环,有n个点, 问从0点出发,经过k步回到原点有多少种方法