仅使用 1、2、3 步即可达到第 n 步

Posted

技术标签:

【中文标题】仅使用 1、2、3 步即可达到第 n 步【英文标题】:Using only 1, 2, 3 steps reach to nth step 【发布时间】:2021-03-24 22:45:28 【问题描述】:

几天前我在做一个在线面试,遇到了这个任务,我们可以通过仅使用步骤 1 或 2 或 3 来达到第 n 步。

更新问题: 一个孩子正在跑上 n 个台阶的楼梯,一次可以跳 1 个台阶、2 个台阶或 3 个台阶。实现一种方法来计算孩子可以用多少种可能的方式跑上楼梯。

这是我使用动态编程的解决方案。但是在面试的过程中,我看到有 6 个隐藏的测试用例失败了。

public static long countWays(int n)
    
        if(n==0 || n==1) return 1;

        long[] res = new long[n + 1];
        res[0] = 1;
        res[1] = 1;
        res[2] = 2;
 
        for (int i = 3; i <= n; i++)
            res[i] = res[i - 1] + res[i - 2]
                     + res[i - 3];
 
        return res[n];
    
 

面试过程中,11个测试用例中只有5个通过,其余6个为隐藏测试用例。

n 的范围是 0 到 10 的 8 次方。

我无法理解我在哪里做错了,这段代码的时间复杂度已经是 O(n)。有没有更好的方法来做到这一点,还是我错过了什么?

【问题讨论】:

如果您在这里解释“步骤”的含义会更清楚。当您写“步骤 1 或 2 或 3”时,听起来您指的是您未描述的某些程序的某些步骤。此外,如果 n 可能高达 10^8,那么您正在执行的算术运算的正确结果不可能适合 long,因为序列呈指数增长。 @Pshemo,我在更新部分添加了详细信息,请检查。 另外,10^8 是一个非常大的数字,O(n) 算法对于大多数在线评委来说可能不够快。 @kaya3,我在更新部分添加了详细信息。程序的返回类型很长,所以我创建了我的数组类型为 long。 您不需要找到所有组合,只需找到它们的数量。正确的答案是计算答案,而不是穷尽找到所有组合。为此,您必须通过纯数学或通过在蛮力方法的输出中找到一个模式来解决一系列小的n 值。 【参考方案1】:

你可以写下这个问题的递归关系

Sk = Sk-1 + Sk-2 + Sk-3

以下矩阵形式:

| S[k]   |   | 1  1  1 |   | S[k-1] |   
| S[k-1] | = | 1  0  0 | * | S[k-2] |
| S[k-2] |   | 0  1  0 |   | S[k-3] |   

这意味着矩阵的k-th 次幂可以很快给你Sk

            k
| 1  1  1 |     | S[2] |   | S[k+2] | 
| 1  0  0 |   * | S[1] | = | S[k+1] |
| 0  1  0 |     | S[0] |   | S[k]   |

所以要解决这个问题,你基本上需要找到这个矩阵的n-th 次方。

您可以使用exponentiation by squaring 算法在O(log n) 时间内将某物提高到n-th 次方。

另一个有用的观察是您只需要 3 个数字 SkSk-1 和 Sk-2 表示矩阵的k-th 次方:

            k
| 1  1  1 |     | S[k] + S[k-1] + S[k-2]    S[k] + S[k-1]      S[k]   |
| 1  0  0 |   = | S[k]                      S[k-1] + S[k-2]    S[k-1] |
| 0  1  0 |     | S[k-1]                    S[k] - S[k-1]      S[k-2] |

由此,通过将矩阵的k-th 和m-th 幂相乘,您可以得到以下关系:

Sk+m = SkSm-2 + Sk-2Sm + Sk-1Sm-2 + Sk-2Sm-1 + Sk-2 sub>Sm-2 + Sk-1Sm-1Sk+m-1 = SkSm-1 + Sk-1Sm + Sk-1Sm-1 sub> + Sk-2Sm-2Sk+m- 2 = Sk-1Sm-2 + Sk-2 Sm-1 + SkSm - S k-1Sm-1

将所有这些放在代码中,您将得到以下解决方案:

public class Solution 
    private static final int M = 1_000_000_007;

    // The set of equations for S[k+m], S[k+m-1], S[k+m-2]
    // a[t] is S[k-t] and b[t] is S[m-t]
    private static long[] mul(long[] a, long[] b) 
        long r0110 = a[0]*b[1] + a[1]*b[0];
        long r0011 = a[0]*b[0] + a[1]*b[1];
        return new long[] 
            (a[0]*b[2] + a[2]*b[0] + r0110 + r0011) % M,
            (a[1]*b[2] + a[2]*b[1] + r0011) % M,
            (r0110 + a[2]*b[2] - a[1]*b[1]) % M
        ;
    

    public static long countWays(int n) 
        long[] result = 1, 0, 0;
        long[] square = 1, 0, 0;
        // Exponentiation by squaring
        for (int i = n; i != 0; i /= 2) 
            if (i % 2 == 1) result = mul(result, square);
            square = mul(square, square);
        
        return result[0];
    

【讨论】:

以上是关于仅使用 1、2、3 步即可达到第 n 步的主要内容,如果未能解决你的问题,请参考以下文章

LeetCode 61——旋转链表

Drupal 仅搜索标题

HC架构使用方式

在管道中的分类器之后使用度量

找到达到多个级别的方法

如何用C语言求1至100所有素数的和?