我在接受采访时遇到了以下问题:
给定一个有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
但是,在采访中,我没有为此编写非常好的代码。你会如何编写这个问题的解决方案?
非常感谢!
答案 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)。这意味着您可以使用以下逻辑通过动态编程解决此问题:
S
,其中包含n + 1个元素,索引为0,1,2,...,n。S[0] = S[1] = 1
S[i] = S[i - 1] + S[i - 2]
。S[n]
。此算法的运行时是一个漂亮的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>
鉴于此问题和手头的问题,您可以计算解决方案暴力(仅进行组合计数)。 “取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:
答案 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)
这是一个加权图问题。
递归函数应该能够处理这个,从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