找到楼梯下的所有路径?

时间:2011-02-24 01:18:59

标签: c++ algorithm dynamic-programming

我在接受采访时遇到了以下问题:

  

给定一个有N个台阶的楼梯,每次可以上升1步或2步。输出从下到上的所有可能方式。

例如:

N = 3

Output :
1 1 1
1 2
2 1

在面试时,我只是说要使用动态编程。

  

S(n)= S(n-1)+1或S(n)= S(n-1)+2

但是,在采访中,我没有为此编写非常好的代码。你会如何编写这个问题的解决方案?

非常感谢!

13 个答案:

答案 0 :(得分:33)

我不会为你编写代码(因为这是一个很好的练习),但这是一个经典的动态编程问题。随着复发,你走在正确的轨道上;这是真的

S(0)= 1

因为如果你在楼梯的底部,只有一种方法可以做到这一点。我们也有那个

S(1)= 1

因为如果你只有一步之遥,你唯一的选择就是向下一步,此时你就在底部。

从那里,很容易找到解决方案数量的重现。如果您考虑一下,您采取的任何步骤都可以作为最后一步采取一小步,或作为最后一步采取一个大步骤。在第一种情况下,n - 1阶梯的每个S(n - 1)解可以通过再迈一步来扩展到解,而在第二种情况下,每个S(n - 2)解到n - 通过两个步骤,可以将2个楼梯外壳扩展为解决方案。这给出了重复

S(n) = S(n - 2) + S(n - 1)

请注意,要评估S(n),您只需要访问S(n - 2)和S(n - 1)。这意味着您可以使用以下逻辑通过动态编程解决此问题:

  1. 创建一个数组S,其中包含n + 1个元素,索引为0,1,2,...,n。
  2. 设置S[0] = S[1] = 1
  3. 对于i从2到n(包括两者),请设置S[i] = S[i - 1] + S[i - 2]
  4. 返回S[n]
  5. 此算法的运行时是一个漂亮的O(n),内存使用量为O(n)。

    然而,可能比这更好。特别是,让我们看一下序列的前几个术语,它们是

     S(0) = 1
     S(1) = 1
     S(2) = 2
     S(3) = 3
     S(4) = 5
    

    这看起来很像Fibonacci序列,事实上你可能会看到

     S(0) = F(1)
     S(1) = F(2)
     S(2) = F(3)
     S(3) = F(4)
     S(4) = F(5)
    

    这表明,通常,S(n)= F(n + 1)。我们实际上可以通过n上的归纳来证明这一点。

    作为我们的基本案例,我们有

    S(0) = 1 = F(1) = F(0 + 1)
    

    S(1) = 1 = F(2) = F(1 + 1)
    

    对于归纳步​​骤,我们得到了

    S(n) = S(n - 2) + S(n - 1) = F(n - 1) + F(n) = F(n + 1)
    

    瞧!我们用Fibonacci数字来编写这个系列。这很好,因为可以在O(1)空间和O(lg n)时间内计算Fibonacci数。有很多方法可以做到这一点。一个人使用

    这一事实

    F(n)=(1 /√(5))(Φ n n

    这里,Φ是黄金比,(1 +√5)/ 2(约1.6),φ是1-Φ,约-0.6。因为第二个项很快就会降到零,所以你可以通过计算获得第n个Fibonacci数

    (1 /√(5))Φ n

    四舍五入。此外,您可以通过重复平方在O(lg n)时间内计算Φ n 。我们的想法是,我们可以使用这种很酷的复发:

    x 0 = 1

    x 2n = x n * x n

    x 2n + 1 = x * x n * x n

    您可以使用快速归纳参数显示此终止于O(lg n)时间,这意味着您可以使用O(1)空间和O(lg n)时间来解决此问题,这是 比DP解决方案更好。

    希望这有帮助!

答案 1 :(得分:13)

你可以概括你的递归函数,也可以采取已经做过的动作。

void steps(n, alreadyTakenSteps) {
    if (n == 0) {
        print already taken steps
    }
    if (n >= 1) {
        steps(n - 1, alreadyTakenSteps.append(1));
    }
    if (n >= 2) {
        steps(n - 2, alreadyTakenSteps.append(2));
    }
}

这不是代码,更多的是伪代码,但它应该给你一个想法。

答案 2 :(得分:4)

你的解决方案听起来不错。

S(n):
    If n = 1 return {1}
    If n = 2 return {2, (1,1)}
    Return S(n-1)x{1} U S(n-2)x{2}

(U是Union,x是Cartesian Product

记住这个是微不足道的,并且会使它成为O(Fib(n))

答案 3 :(得分:4)

@templatetypedef给出了很好的答案 - 我把这个问题作为一个练习来完成,并在不同的路线上得到斐波那契数字:

问题基本上可以简化为Binomial coefficients的应用,这对于组合问题很方便:一次取k个n个组合的数量(称为n选择k)可以通过等式找到/ p>

enter image description here

鉴于此问题和手头的问题,您可以计算解决方案暴力(仅进行组合计数)。 “取2步”的数量必须至少为零,并且最多可以为50,因此组合的数量是0(= k <= 50的C(n,k)的总和(n =做出的决定,k =从那些n中取出2的数量

BigInteger combinationCount = 0;
for (int k = 0; k <= 50; k++)
{
    int n = 100 - k;
    BigInteger result = Fact(n) / (Fact(k) * Fact(n - k));
    combinationCount += result;
}

这些二项式系数的总和只是happens to also have a different formula

enter image description here

答案 4 :(得分:2)

实际上,你可以证明爬升的方式只是斐波那契序列。这里有很好的解释:http://theory.cs.uvic.ca/amof/e_fiboI.htm

答案 5 :(得分:1)

解决问题,并使用动态编程解决方案解决问题可能是两回事。

http://en.wikipedia.org/wiki/Dynamic_programming

  

一般来说,为了解决给定的问题,我们需要解决问题的不同部分(子问题),然后结合子问题的解决方案来达到整体解决方案。通常,这些子问题中的许多都是相同的。动态编程方法只寻求解决每个子问题一次,从而减少计算次数

这让我相信您希望找到同时为Recursive的解决方案,并使用Memo Design Pattern。递归通过将其分解为子问题来解决问题,并且Memo设计模式允许您缓存答案,从而避免重新计算。 (请注意,有些缓存实现可能不是Memo设计模式,您也可以使用其中一种)。

求解

我将采取的第一步是手动解决一些问题,增加或增加N的大小。这将为您提供一个模式来帮助您找到解决方案。从N = 1开始,通过N = 5.(正如其他人所说,它可能是fibbonacci序列的一种形式,但我会在调用问题解决并理解之前为自己确定这一点。)

从那里,我会尝试制作一个使用递归的通用解决方案。递归通过将其分解为子问题来解决问题。

从那里开始,我会尝试将以前的问题输入缓存到相应的输出中,从而记住它,并制作一个涉及“动态编程”的解决方案。

即,您的某个功能的输入可能是2, 5,正确的结果是7。创建一些从现有列表或字典中查找的函数(基于输入)。它将查找使用输入2, 5进行的调用。如果找不到,请调用函数进行计算,然后存储并返回答案(7)。如果确实找到了,请不要费心计算它,并返回先前计算的答案。

答案 6 :(得分:1)

以下是非常简单的CSharp中这个问题的简单解决方案(我相信你可以通过几乎不改变Java / C ++来移植它)。 我添加了一点复杂性(增加了你可以走3步的可能性)。如果需要,您可以将此代码概括为“从1到k步”,并添加步骤中的while循环(last if语句)。

我使用了动态编程和递归的组合。动态编程的使用避免了重新计算每个前一步骤;减少与调用堆栈相关的空间和时间复杂度。然而,它增加了一些空间复杂度(O(maxSteps)),我认为与增益相比可以忽略不计。

/// <summary>
/// Given a staircase with N steps, you can go up with 1 or 2 or 3 steps each time.
/// Output all possible way you go from bottom to top
/// </summary>
public class NStepsHop
{
    const int maxSteps = 500;  // this is arbitrary
    static long[] HistorySumSteps = new long[maxSteps];

    public static long CountWays(int n)
    {
        if (n >= 0 && HistorySumSteps[n] != 0)
        {
            return HistorySumSteps[n];
        }

        long currentSteps = 0;
        if (n < 0)
        {
            return 0;
        }
        else if (n == 0)
        {
            currentSteps = 1;
        }
        else
        {
            currentSteps = CountWays(n - 1) + 
                           CountWays(n - 2) + 
                           CountWays(n - 3);
        }

        HistorySumSteps[n] = currentSteps;
        return currentSteps;
    }
}

您可以按以下方式调用

long result;
result = NStepsHop.CountWays(0);    // result = 1
result = NStepsHop.CountWays(1);    // result = 1
result = NStepsHop.CountWays(5);    // result = 13
result = NStepsHop.CountWays(10);   // result = 274
result = NStepsHop.CountWays(25);   // result = 2555757

你可以争论n = 0时的初始情况,它可以是0,而不是1.我决定选择1,但修改这个假设是微不足道的。

答案 7 :(得分:1)

使用递归可以很好地解决问题:

void printSteps(int n)
{
   char* output = new char[n+1];
   generatePath(n, output, 0);
   printf("\n");
}

void generatePath(int n, char* out, int recLvl)
{
   if (n==0)
   {
      out[recLvl] = '\0';
      printf("%s\n",out);
   }

   if(n>=1)
   {
      out[recLvl] = '1';
      generatePath(n-1,out,recLvl+1);
   }

   if(n>=2)
   {
      out[recLvl] = '2';
      generatePath(n-2,out,recLvl+1);
   }
}

并在主要:

void main()
{
    printSteps(0);
    printSteps(3);
    printSteps(4);
    return 0;       
}

答案 8 :(得分:0)

这是一个加权图问题。

  • 从0开始,你只能1路(0-1)。
  • 你可以从0和1(0-2,1-1)两种方式获得。
  • 你可以达到3种方式,从1和2(2有两种方式)。
  • 你可以达到5种方式,从2和3(2种有2种方式,3种有3种方式)。
  • 你可以达到5种方式......

递归函数应该能够处理这个,从N开始向后工作。

答案 9 :(得分:0)

为此

完成C-Sharp代码
 void PrintAllWays(int n, string str) 
    {
        string str1 = str;
        StringBuilder sb = new StringBuilder(str1);
        if (n == 0) 
        {
            Console.WriteLine(str1);
            return;
        }
        if (n >= 1) 
        {
            sb = new StringBuilder(str1);
            PrintAllWays(n - 1, sb.Append("1").ToString());
        }
        if (n >= 2) 
        {
            sb = new StringBuilder(str1);
            PrintAllWays(n - 2, sb.Append("2").ToString());
        }
    }

答案 10 :(得分:0)

基于C的迟回答

#include <stdio.h>
#include <stdlib.h>
#define steps 60
static long long unsigned int MAP[steps + 1] = {1 , 1 , 2 , 0,};

static long long unsigned int countPossibilities(unsigned int n) {
    if (!MAP[n]) {
       MAP[n] = countPossibilities(n-1) + countPossibilities(n-2);
    }
    return MAP[n];
}

int main() {
   printf("%llu",countPossibilities(steps));
}

答案 11 :(得分:0)

这是一个C ++解决方案。这将打印给定楼梯数的所有可能路径。

// Utility function to print a Vector of Vectors
void printVecOfVec(vector< vector<unsigned int> > vecOfVec)
{
    for (unsigned int i = 0; i < vecOfVec.size(); i++)
    {
        for (unsigned int j = 0; j < vecOfVec[i].size(); j++)
        {
            cout << vecOfVec[i][j] << " ";
        }
        cout <<  endl;
    }
    cout << endl;
}

// Given a source vector and a number, it appends the number to each source vectors
// and puts the final values in the destination vector
void appendElementToVector(vector< vector <unsigned int> > src,
                           unsigned int num,
                           vector< vector <unsigned int> > &dest)
{
    for (int i = 0; i < src.size(); i++)
    {
        src[i].push_back(num);
        dest.push_back(src[i]);
    }
}

// Ladder Problem
void ladderDynamic(int number)
{
    vector< vector<unsigned int> > vecNminusTwo = {{}};
    vector< vector<unsigned int> > vecNminusOne = parse deploy;
    vector< vector<unsigned int> > vecResult;

    for (int i = 2; i <= number; i++)
    {
        // Empty the result vector to hold fresh set
        vecResult.clear();

        // Append '2' to all N-2 ladder positions
        appendElementToVector(vecNminusTwo, 2, vecResult);

        // Append '1' to all N-1 ladder positions
        appendElementToVector(vecNminusOne, 1, vecResult);

        vecNminusTwo = vecNminusOne;
        vecNminusOne = vecResult;
    }

    printVecOfVec(vecResult);
}

int main()
{
    ladderDynamic(6);
    return 0;
}

答案 12 :(得分:-1)

可能是我错了..但它应该是:

S(1) =0
S(2) =1

这里我们正在以这种方式考虑排列

S(3) =3
S(4) =7