降低递归斐波那契类函数在 C++ 中的时间复杂度

Reducing the time complexity of recursive Fibonacci like function in c++

我一直在尝试用 C++ 编写解决问题的代码。

这只能使用递归来解决 取模 10000000007 不是代码耗时较长的问题 with/without 它

问题: 戴维斯喜欢一次爬楼梯 1、2 或 3 步。

给定 n 个楼梯各自的高度,找出并打印他可以爬上每个楼梯的方式数,模 10^10 + 7

例子 对于 n=5

楼梯有 5 级台阶。戴维斯可以按以下步骤顺序进行操作:

1 1 1 1 1

1 1 1 2

1 1 2 1

1 2 1 1

2 1 1 1

1 2 2

2 2 1

2 1 2

1 1 3

1 3 1

3 1 1

2 3

3 2

他可以采取这 5 个步骤的 13 种可能方式和 13 模 10000000007 = 13

到目前为止我使用递归的解决方案:

int ways(int n) {
    if(n==1) return 1;
    if(n==2) return 2;
    if(n==3) return 4;
    return (ways(n-3)+ways(n-2)+ways(n-1))%10000000007;
}

该代码运行完美,但在大型计算上花费的时间太长。 如果有优化它的方法,请分享。 解决方案越简单越好。 谢谢

您可以开始添加记忆,以避免大多数递归调用。

long long ways(long long n)
{
    // Memoization requires to store the previously calculated values.
    static std::map<long long, long long> mem{
        {1, 1}, {2, 2}, {3, 4}
    };

    // I'm using std::map, but I don't want to use operator[], nor find...
    // https://en.cppreference.com/w/cpp/container/map/operator_at
    // https://en.cppreference.com/w/cpp/container/map/find
    // https://en.cppreference.com/w/cpp/container/map/lower_bound
    auto it = mem.lower_bound(n);

    // If it has already been calculated, return the stored value.
    if ( it != mem.cend() and it->first == n )
        return it->second;

    // Otherwise evaluate the new value (recursively) and store it
    // Note that I can use the iterator as an hint for the insertion.
    // https://en.cppreference.com/w/cpp/container/map/emplace_hint
    return mem.emplace_hint(
        it, n,
        (ways(n-3) + ways(n-2) + ways(n-1)) % 10000000007
    )->second;
}

如果这还不够,您将不得不寻找一些数学技巧,或者完全避免递归并使用简单的循环。

该算法计算自定义 Tribonacci 序列 模数 10000000007。这可以在 O(log n) 时间和 O(1) space 基于序列的 矩阵表示 ,例如 Fibonacci。有关详细信息,请参阅 this post and this answer

这种方法对于大输入非常快(比使用记忆快得多)并且非常节省内存。

但是,有一个问题:实现需要计算两个 64 位整数与 64 位整数的模相乘(除非模数可以容纳 32 位整数)。大多数平台不支持 128 位整数,但可以使用 boost::multiprecision。有关详细信息,请参阅 this post

这是一个实现:

int64_t int128_mulMod(int64_t a, int64_t b, int64_t mod)
{
    return int64_t((int128_t(a) * int128_t(b)) % int128_t(mod));
}

// Compute the matrix multiplication of a * b and put the result in a
void matMulMod(int64_t a[3][3], int64_t b[3][3], int64_t mod)
{
    int64_t res[3][3] = {};

    for(int i=0 ; i<3 ; ++i)
        for(int j=0 ; j<3 ; ++j)
            for(int k=0 ; k<3 ; ++k)
                res[i][j] += int128_mulMod(a[i][k], b[k][j], mod);

    for(int i=0 ; i<3 ; ++i)
        for(int j=0 ; j<3 ; ++j)
            a[i][j] = res[i][j];
}

// Compute the matrix exponentiation a^n and put the result in a
void matPowMod(int64_t a[3][3], int64_t n, int64_t mod)
{
    if(n == 0 || n == 1)
        return;

    int64_t m[3][3] = {{ 1, 1, 1 },
                       { 1, 0, 0 },
                       { 0, 1, 0 }};

    // Use recursion to square the matrix efficiently
    matPowMod(a, n / 2, mod);

    // Square the matrix
    matMulMod(a, a, mod);

    // Remainder case
    if(n % 2)
        matMulMod(a, m, mod);

    // Apply the modulus on each term to prevent overflows
    for(int i=0 ; i<3 ; ++i)
        for(int j=0 ; j<3 ; ++j)
            a[i][j] %= mod;
}

int64_t ways_fast(int64_t n)
{
    int64_t a[3][3] = {{ 1, 1, 1 },
                       { 1, 0, 0 },
                       { 0, 1, 0 }};

    if(n==1) return 1;
    if(n==2) return 2;
    if(n==3) return 4;

    matPowMod(a, n, 10000000007ll);
    return a[0][0]; // Result of the Tribonacci sequence
}

例如,在我的机器上,ways_fast(30'000) 比计算@Bob__ 提供的基于记忆的方法快 100 倍。此外,记忆算法在计算 ways(60'000) 时崩溃,而 ways_fast(200'000) 正常工作。

更多信息请阅读: