问题描述
假设我们有两种方法来爬一个n级的台阶,我们可以一次爬一级台阶,也可以爬两级台阶。那么我们爬完这n级台阶总共有多少种方法呢?
分析
关于这个问题的分析和讨论网上已经非常多了。这里一方面分析一下这个问题的本质,另外也对同类型的问题做一个总结概括。对于这种问题,我们先来看一些简单的情况,假设所有走法的函数为f(n),在只有一级台阶的时候,我们的走法如下:
f(1) = 1, 因为我们只需要走一级台阶就到达了目的地。
而对于0级台阶,我们可以认为什么都不走也算一种走法,那么f(0) = 1。
再往后推导,那么f(2)呢?对于两级台阶来说,我们有如下的走法:
1. 每次走一级。 2. 一次走两级 所以f(2) = 2
通过这几步的观察,我们可以发现一个如下规律:
f(0) = 1
f(1) = 1
f(n) = f(n - 1) + f(n - 2) n >= 2
从我们观察的结果来看,这个递推关系构成了Fibonacci数列。当然,我们的这个推导是否就一定正确呢?我们可以通过数学归纳法来证明。假设我们这个跳台阶的关系成立,那么我们来针对两种情况考虑:
基础情况: 对于n = 0, 1的情况,显然f(0) = 1, f(1) = 1, f(2) = f(0) + f(1), 结论成立。
假定对于数字n结论都成立,那么f(n) = f(n - 1) + f(n - 2) (n >= 2)。 而此时,针对n + 1级台阶来说,能一步走到第n + 1级的台阶只能是首先走到了第n阶或者第n - 1阶。当走到第n阶的时候,只需要走一级台阶就达到了。而到第n-1阶的时候,只需要走两级台阶就达到了。所以它对应着走到第n阶再走一步,或者走到第n-1阶再走两个台阶。而当我们走到第n阶的时候,意味着我们这个时候的走法是f(n),对应的,走到第n-1阶的时候走法是f(n - 1)。那么,这个时候我们走到第n+1级台阶总共的走法应该是f(n + 1) = f(n) + f(n - 1)。
这样我们就证明了前面的这个推论。在前面的推导里,有人可能会有点疑惑,在第n - 1步的时候,既然前面有两个台阶到第n +1级,那么从这里一次走一个台阶到目的地这种走法为什么不能算呢?因为这个时候从n-1走一步到第n级的时候这个整体的走法已经包含在f(n)里面了。
有了前面的这些推论,我们知道问题已经归结为怎么去计算Fibonacci数列。
Fibonacci数列问题
根据前面的讨论,我们从这个数列的特性来实现怎么计算它们的所有走法。最典型的实现思路如下:
public static long fibonacci(int n) { if(n == 0) return 1; if(n == 1) return 1; return fibonacci(n - 1) + fibonacci(n - 2); }我们知道,因为这里递归函数的关系为f(n) = f(n - 1) + f(n -2),它的增长将很快,这种实现很容易导致堆栈溢出。至于这个函数会是一个什么样的形式,他们增长的有多快呢?我们后面会继续讨论。这里先把几种代码实现的思路记录下来。
前面的代码实现是简单,不过问题在于大量的递归占用了空间,而实际上并没有用到那么多。我们完全可以把中间部分计算的结果保存起来,不需要重复的递归来计算。为了保存这个结果,我们可以采用一个长度为给定数字的数组。按照这种思路,我们实现的代码如下:
public static long f(int n) { if(n < 0) throw new IllegalArgumentException("Invalid n"); long[] result = new long[n]; result[0] = 1; result[1] = 1; if(n == 0) return 1; if(n == 1) return 1; for(int i = 2; i < n; i++) { result[i] = result[i - 1] + result[i - 2]; } return result[n - 1]; }这种代码实现的思路是保存了中间计算的结果,然后省略了很多重复计算的步骤。 从算法的时间复杂度来说,它会简单很多,只有O(n)。当然,从空间复杂度来说,它达到了O(n)。从代码里我们也看到,实际上每次我们用到的结果都是取一个元素它前面的两个,然后过去了之后再之前的就不用了。那么,这么点空间是否可以重复利用呢?如果可以的话,我们可以在时间复杂度不变的情况下,把占用的空间给缩减下来。下面是另外一个实现:
public static long compute(int n) { if(n < 0) throw new IllegalArgumentException("Invalid n"); if(n == 0) return 1; if(n == 1) return 1; int first = 1, second = 1; int sum = first + second; for(int i = 2; i < n; i++) { first = second; second = sum; sum = first + second; } return sum; }这部分的代码改进就在for循环里。每次我们将second赋值给first,然后将sum再赋值给second。采用一种滚动式向前推进的方式。只需要3个临时变量就解决了。
当然,在这方面的讨论有很多,解决方法也有很多,这只是一种比较简单直接的办法而已。
进一步推导
前面我们描述的Fibonacci问题它其实对应的是一种如下递归序列的一个特殊形式:
f(n) = a1f(n - 1) + a2f(n-2) + ... + adf(n-d)。 对于这种递归函数,到底有没有一个直接的多项式函数形式的描述呢?相对来说,这是一个很复杂的问题,一种思路是可以建立特征值等式,然后建立一个方程组,结合递归函数的一些初始条件,比如f(0)=xxx f(1)=xxx来推导。这里基于的一个假设是f(n)最终应该会演化为一个多项式的形式,也就是f(n) = a1x^(n) + a2x^(n-1) + ...+ adx^(n-d) + an。
因为这里的证明过于繁琐,可以参考后面的参考材料,这里就不再赘述。
总结
跳台阶的问题解决思路其实就是绕了两步,第一步要推导出它们符合一个Fibonacci数列的特性。第二步则是要求这个Fibonacci数列的值。所以问题的焦点就变成对Fibonacci数列问题的分析。而且,对于这个数列的数学复杂度分析也比较有意思。它对应一个指数函数的级别,对它的推导曾经花了好几个世纪。
参考材料
mathematics for computer science
相关推荐
这篇文章主要介绍了在Python语言中解决三个经典的递归问题:跳台阶问题、变态跳台阶问题以及矩形覆盖问题。这三个问题虽然描述不同,但实际上都可以归结为斐波那契数列的变种。通过示例代码的形式,作者详细地介绍了...
### Python解决N阶台阶走法问题的方法分析 在计算机科学领域,解决特定问题时经常会遇到经典的数学问题,比如“走台阶”问题。这类问题看似简单却蕴含着丰富的数学原理和编程思维,尤其对于理解递归与动态规划等...
跳台阶问题是一个经典的计算机科学问题,它涉及到递归算法的运用。在本问题中,一个楼梯有 n 级台阶,每次跳跃可以跳 1 级或 2 级。目标是找出到达顶层有多少种不同的跳法。这个问题可以通过数学分析和递归算法来...
跳台阶问题是一个经典的动态规划和递归问题,在C语言中可以通过循环或递归的方式来解决。这个问题的基本设定是:有一个含有n级台阶的楼梯,每次可以跳1级或2级,求解到达顶部的不同跳法数量。 首先,我们可以观察到...
在PHP编程中,青蛙跳台阶问题是一个典型的递归或动态规划问题。问题的背景是有一只青蛙,它能一次跳1级或2级台阶,问跳上n级台阶有多少种跳法。这个问题与著名的斐波那契数列(Fibonacci sequence)紧密相关。 首先...
跳台阶问题 题目: 一个台阶总共有 n 级,如果一次可以跳 1 级,也可以跳 2 级。 求总共有多少总跳法,并分析算法的时间复杂度。 分析: 也是比较基础的题目,通过递归可以方便的求解 代码实现如下(GCC编译通过):...
这个问题通常设定为:一个人每次可以跳1个或2个台阶,如果楼梯有n级台阶,那么这个人到达顶层有多少种不同的方式。这是一个典型的数学问题,通过编程可以更直观地找到解决方案。 标签中的“java”表明了这个项目是...
总结来说,变态跳台阶问题是一个典型的动态规划问题,但通过观察和分析,我们可以找到一个更高效的解决方案,利用指数运算快速得到结果。这种方法不仅减少了计算量,而且提高了算法的运行效率。在实际编程中,理解和...
在这个"html5实现的变色弹珠跳台阶游戏源码.zip"压缩包中,包含了一个使用HTML5编写的弹珠跳台阶游戏。游戏的核心是通过JavaScript进行动态控制,结合HTML5的Canvas元素来绘制图形和处理游戏逻辑。 首先,Canvas是...
【Java变态跳台阶问题】是一个经典的动态规划问题,也被称为“斐波那契青蛙跳台阶”。此问题源自于数学中的斐波那契数列,但与标准的斐波那契序列有所不同,因为它允许一步跳跃任意阶数的台阶。 1. **问题描述**: ...
在本实例中,我们讨论的是一个基于HTML5的变色弹球跳台阶小游戏,这个小游戏利用HTML5的Canvas元素以及JavaScript编程来实现。 Canvas是HTML5中的一个重要组成部分,它是一个用于在网页上绘制图形的画布,通过...
爬台阶问题通常被称为“猴子取香蕉”或“青蛙跳台阶”,其基本设定是:一只猴子站在地面上,面对一串n个台阶,每次它可以跳1个或2个台阶。问题在于,猴子有多少种不同的方式可以跳上所有台阶。这是一个典型的动态...
这个问题是经典的动态规划问题,通常被称为“青蛙跳台阶”或者“斐波那契数列”的变种。我们来深入分析一下解题思路。 首先,题目描述了一只青蛙要跳上n级台阶,每次它可以跳1级或2级。我们要找出青蛙跳上n级台阶的...
青蛙跳台阶问题分析(实质上就是斐波那切数列)√ 磁盘容量大小排序 √ 二分查找 √ 冒泡排序 √ 选择排序 √ 插入排序 √ 快速排序 √ 希尔排序 归并排序 桶排序 基数排序 × 一个整数分解成两个质数和 √ 数据分类...
【技术分析】 HTML,是一种制作万维网页面的标准语言,它消除了不同计算机之间信息交流的障碍; CSS,可以帮助把网页外观做得更加美观; JavaScript,是一种轻量级的解释型编程语言; jQuery,使用户能更方便地处理...
【jQuery变色弹珠跳台阶小游戏代码】是一个基于jQuery、HTML5技术开发的趣味互动小游戏。这款游戏设计简单但富有挑战性,玩家通过点击台阶来改变台阶的颜色,红色、黄色、紫色三种颜色交替出现,目标是让弹珠在变色...