【每日算法Day 80】所有人都會做的入門題,高級解法來了!

題目還是昨天的題,昨天已經介紹了三種解法了,今天介紹一個最快的方法。

題目鏈接

LeetCode 面試題 08.01. 三步問題[1]

題目描述

三步問題。有個小孩正在上樓梯,樓梯有 n 階臺階,小孩一次可以上 1 階、2 階或 3 階。實現一種方法,計算小孩有多少種上樓梯的方式。結果可能很大,你需要對結果模 1000000007

示例1

        輸入:
n = 3 
輸出:
4
解釋:
有四種走法

      

示例2

        輸入:
n = 5
輸出:
13

      

題解

昨天的題解地址:
韋陽的博客:【每日算法Day 79】所有人都會做的入門題,但是能看出你的代碼能力![2]

知乎專欄:【每日算法Day 79】所有人都會做的入門題,但是能看出你的代碼能力![3]

昨天是通過動態規劃來解決的,轉移方程爲:

f[i] = f[i-1] + f[i-2] + f[i-3] \\

初始情況是 f[1] = 1, f[2] = 2, f[3] = 4

如果通過遞推的方式來求解的話,時間複雜度是 O(n) ,但是我們還可以用矩陣快速冪的方法加速到 O(\log n)

相信大家快速冪一定聽說過(沒聽說過當我沒說),如果讓你求 a^n,那麼可以分兩種情況。如果 n 是偶數,那麼可以計算 a^{n/2},然後求它的平方 (a^{n/2})^2 就行了。如果 n 是奇數,那麼可以計算 a^{(n-1)/2},然後求它的平方 a \cdot (a^{(n-1)/2})^2 就行了。這樣只需要用 O(\log n) 的複雜度就可以計算出 a^n 了。

類似的,如果我們要計算一個矩陣的冪 A^n,也可以將其拆分成兩半,然後計算一半再平方就行了。

那麼這題跟矩陣有什麼關係呢?如果我們把轉移方程右邊三項寫成向量形式:

\begin{bmatrix}     f_{i-3} & f_{i-2} & f_{i-1} \end{bmatrix} \\

那麼給它右邊乘上一個矩陣 A

\begin{bmatrix}     0 & 0 & 1 \\\     1 & 0 & 1 \\\     0 & 1 & 1 \end{bmatrix} \\

那麼就會得到向量:

\begin{bmatrix}     f_{i-2} & f_{i-1} & f_{i-1}+f_{i-2}+f_{i-3} \end{bmatrix} \\

即:

\begin{bmatrix}     f_{i-2} & f_{i-1} & f_{i} \end{bmatrix} \\

所以乘一次矩陣 A 就可以得到下一個 f 值,那麼從初始的向量 \begin{bmatrix}1 & 2 & 4\end{bmatrix} 開始,乘上 A^{n-3} 就可以得到 \begin{bmatrix}f_{n-2} & f_{n-1} & f_{n}\end{bmatrix} 了。

而這裏的 A^{n-3} 就可以通過矩陣快速冪來計算得到。

代碼

c++

        typedef long long ll;
typedef vector<vector<ll>> vvl;
typedef vector<ll> vl;
const ll p = 1e9+7;

class Solution {
public:
    vvl mat_mul(vvl& A, vvl& B) {
        int a = A.size(), b = B.size(), c = B[0].size();
        vvl C(a, vl(c, 0));
        for (int i = 0; i < a; ++i) {
            for (int j = 0; j < c; ++j) {
                for (int k = 0; k < b; ++k) {
                    (C[i][j] += A[i][k] * B[k][j]) %= p;
                }
            }
        }
        return C;
    }

    vvl mat_pow(vvl& A, int n) {
        int m = A.size();
        vvl B(m, vl(m, 0));
        for (int i = 0; i < m; ++i) B[i][i] = 1;
        while (n > 0) {
            if (n&1) B = mat_mul(B, A);
            A = mat_mul(A, A);
            n >>= 1;
        }
        return B;
    }

    vvl mat_pow_rec(vvl& A, int n) {
        if (n == 1) return A;
        vvl B = mat_pow_rec(A, n/2);
        B = mat_mul(B, B);
        if (n&1) return mat_mul(A, B);
        return B;
    }

    int waysToStep(int n) {
        vl f = {1, 2, 4};
        if (n <= 3) return f[n-1];
        vvl A = {{0, 0, 1}, {1, 0, 1}, {0, 1, 1}};
        vvl B = mat_pow(A, n-3);
        ll res = 0;
        for (int i = 0; i < 3; ++i) {
            (res += f[i] * B[i][2]) %= p;
        }
        return res;
    }
};

      

python

        import numpy as np

class Solution:
    def mat_pow(self, A, n):
        m = A.shape[0]
        B = np.eye(m, dtype=np.int64)
        while n > 0:
            if (n&1)!=0:
                B = np.mod(np.matmul(B, A), self.p).astype(np.int64)
            A = np.mod(np.matmul(A, A), self.p).astype(np.int64)
            n >>= 1
        return B;

    def waysToStep(self, n: int) -> int:
        self.p = int(1e9+7)
        f = [1, 2, 4]
        if n <= 3: return f[n-1]
        A = np.array([[0, 0, 1], [1, 0, 1], [0, 1, 1]], dtype=np.int64)
        B = self.mat_pow(A, n-3)
        res = 0
        for i in range(3):
            res += f[i] * B[i][2]
        return int(res%self.p)

      

參考資料

[1]

LeetCode 面試題 08.01. 三步問題: leetcode-cn.com/problem

[2]

韋陽的博客:【每日算法Day 79】所有人都會做的入門題,但是能看出你的代碼能力!: godweiyang.com/2020/03/

[3]

知乎專欄:【每日算法Day 79】所有人都會做的入門題,但是能看出你的代碼能力!: zhuanlan.zhihu.com/p/11

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章