Leetcode 面试题 08.01. 三步问题

三步问题。有个小孩正在上楼梯,楼梯有n阶台阶,小孩一次可以上1阶、2阶或3阶。实现一种方法,计算小孩有多少种上楼梯的方式。结果可能很大,你需要对结果模1000000007。

示例1:

 输入:n = 3 

 输出:4

 说明: 有四种走法

示例2:

 输入:n = 5

 输出:13

提示:

  1. n范围在[1, 1000000]之间

**难度**: Easy

**标签**: 动态规划、


# -*- coding: utf-8 -*-
# @Author  : LG

"""
超时
解题思路:
    动态规划,当n非常大时,需要循环计算n次,超时
"""
class Solution:
    def waysToStep(self, n: int) -> int:
        if n == 1:
            return 1
        if n == 2:
            return 2
        if n == 3:
            return 4

        dp = [[] for _ in range(n)]
        dp[0] = 1
        dp[1] = 2
        dp[2] = 4

        for i in range(3, n):
            dp[i] = dp[i-1] + dp[i-2] + dp[i-3]
        return dp[-1]%1000000007

"""
执行用时:2020 ms, 在所有 Python3 提交中击败了5.18% 的用户
内存消耗:29.1 MB, 在所有 Python3 提交中击败了51.83% 的用户


解题思路:
    优化,快速幂
                                  [[1,1,0],
    [ dp[i-1], dp[i-2], dp[i-3]]*  [1,0,1],  = dp[i-1]+dp[i-2]+dp[i-3], dp[i-1], dp[i-2]
                                   [1,0,0]]
    
    通过上式,可以快速得出,dp[i] = dp[i-1]+dp[i-2]+dp[i-3]
    
    可得出:
                                              [[1,1,0],
        [dp[i], dp[i-1], dp[i-2]] = [1,2,4] *  [1,0,1], ^^n-3
                                              [1,0,0]]
    也就是,[1,2,4] * A ** (n-3)
    为快速计算A**n,使用快速幂方法计算
    
    快速幂:
        计算a**n 次方时,
         若n为偶数,可先计算 a**(n/2) ,然后计算 (a**(n/2))**2
         若n为奇数,可先计算 a**((n-1)/2), 然后计算 a*a**((n-1)/2)**2  
         
"""
import numpy as np

class Solution:
    def waysToStep(self, n: int) -> int:
        if n == 1:
            return 1
        if n == 2:
            return 2
        if n == 3:
            return 4
        if n == 4:
            return 7
        init_array = np.array([4, 2, 1], dtype=np.int64)
        A = np.array([[1, 1, 0], [1, 0, 1], [1, 0, 0]], dtype=np.int64)
        A1 = A.astype(np.int64)
        if (n - 3) % 2 == 0:
            for i in range(int((n - 3) / 2 - 1)):
                A1 = np.matmul(A1, A).astype(np.int64)
                A1 = np.mod(A1, 1000000007)
            A = np.matmul(A1, A1).astype(np.int64)

        else:
            for i in range(int((n - 4) / 2 -1)):
                A1 = np.matmul(A1, A)
                A1 = np.mod(A1, 1000000007)
            A1 = np.matmul(A1, A1)
            A = np.matmul(A, A1)
        A = np.mod(A, 1000000007)
        return int(np.mod(np.matmul(init_array, A)[0], 1000000007))

"""
执行用时:108 ms, 在所有 Python3 提交中击败了99.12% 的用户
内存消耗:29.3 MB, 在所有 Python3 提交中击败了50.50% 的用户
    
解题思路:
    更深入的,我们将二分的快速幂,再细分
    如:  a**8 = ((a**2)**2)**2      
    如:  a**7 = a*((a**2)**2)
    
    例:  a**n
            n = 6时       偶数,计算当前次方      记录奇数
        n = 6               a                 
        n = 6 >> 1 = 3      a**2 = a**2         a**2   
        n = 3 >> 1 = 1      a**2 = a**4 
        
                                    a**4 * a**2 = a**6
        
            n = 7时       n**7 
        n = 7               a                   a
        n = 7 >> 1 = 3      a**2 = a**2         a**2
        n = 3 >> 1 = 1      a**2 = a**4         
        
                                    a**4 * a * a**2 = a**7
                                    
            n = 15时      n**15      
        n = 15               a                   a
        n = 15 >> 1 = 7     a**2 = a**2          a**2
        n = 7 >> 1 = 3      a**2 = a**4          a**4
        n = 3 >> 1 = 1      a**2 = a**8          
        
                                    a**8 * a * a**2 * a**4 = a**15 
    可以看出,n依次除以2向下取整,并对a累次方,
        当遇到n为奇数时,记录当前次方数,
        最后将最终的次方数与 奇数时记录的 所有相乘即可
    
    具体实现见代码注释
"""
import numpy as np

class Solution:
    def waysToStep(self, n: int) -> int:
        if n == 1:
            return 1
        if n == 2:
            return 2
        if n == 3:
            return 4

        init_array = np.array([4, 2, 1], dtype=np.int64)
        A = np.array([[1, 1, 0], [1, 0, 1], [1, 0, 0]]).astype(np.int64)

        A = self.mat_pow(A, n-3)
        init_array = np.matmul(init_array, A).astype(np.int64)[0]
        return int(init_array % 1000000007)

    def mat_pow(self, A, n):
        e = np.eye(3, dtype=np.int64)  # 用于累计 奇数时的 次方乘积
        while n > 0:
            if (n & 1) != 0:    # 判断奇偶
                e = np.matmul(e, A)    # 当是奇数时, 累计当前次方乘积
                e = np.mod(e, 1000000007)   # 取模
            A = np.matmul(A, A).astype(np.int64)    # 计算当前次方和
            A = np.mod(A, 1000000007)
            n = n >> 1  # /2向下取整
        return e