剑指Offer——跳台阶
题目描述
一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
思路分析
这个问题可以先从简单开始考虑,台阶只有1阶,只有1种跳法,台阶有2阶,有2种跳法:一种两次跳一级;另一种一次跳两级。然后考虑一般情况,当有n级台阶时,将f(n)作为总跳法,第1次跳的时候,可以有两种方法:一是跳一级,此时跳法数目等于后面剩下的n-1级台阶的跳法,即f(n-1);二是跳两级,此时跳法数目等于后面剩下的n-2级台阶的跳法。所以,n级台阶的跳法总数:f(n)=f(n-1)+f(n-2)。从这里可以看出,本题是斐波那契数列的一种变形。
但本题还有一种思路,就是将跳1级与跳2级看成排列组合问题,假设x为跳1级台阶的次数,y为跳2级台阶的次数,于是可知x+2y=n,即x=n-2y,这里可以看作对x与y的排列组合问题,即C(x+y,y)=C(n-y,y)。其中0<=y<=n/2。
代码实现
斐波那契数列实现
1、递归实现
相信很多人第一次接触到斐波那契数列的时候,就是使用递归实现,该实现简单直观,但该算法效率不高,因为递归会反复计算相同的子问题,随着n的增大,计算量也急剧增大,时间复杂度为T(n) = O(1.618 ^ n)。代码如下:
public int recurFib(int n) { if (n <= 0) { return 0; } if (n == 1 || n == 2) { return n; } return recurFib(n - 1) + recurFib(n - 2); }
2、递推实现
使用递归实现的算法之所以效率太低,是因为重复计算太多,所以我们可以将中间结果保存,当再次计算的时候先查找一下。但有一种更简单的方法,就是从下向上计算,递归是从上到下计算,上面会依赖下面的值,因此会导致重复计算。我们使用从下往上计算,没有值依赖,算法复杂度就降为O(n)。首先根据f(1)和f(2)算出f(3),f(2)和f(3)算出f(4),以此类推即可算出f(n),实现代码如下:
public int recursiveFib(int n) { if (n <= 0) { return 0; } if (n == 1 || n == 2) { return n; } int fibN = 0; int fibNMinusOne = 2; int fibNMinusTwo = 1; for (int i = 3; i <= n; i++) { fibN = fibNMinusOne + fibNMinusTwo; fibNMinusTwo = fibNMinusOne; fibNMinusOne = fibN; } return fibN; }
3、矩阵法实现
还有一种矩阵法实现,算法效率最高,时间复杂度为O(logn),该算法是根据下面的公式:
上面的公式可以数学归纳法证明,感兴趣的可以自己搜索一下相关资料,有了上面的公式,就将求解f(n),转换成求二阶矩阵的n次方,然后取结果的第1行第2列即可。如果仅仅是矩阵的乘法,时间复杂度依旧为O(n),但可以使用快速幂的方法求解一个数的乘法。乘方的性质如下:
结合上面两个公式代码如下:
public class Solution { public int fib(int n) { if (n <= 0) { return 0; } if (n <= 2) { return n; } int[][] unitMatrix = {{1, 1}, {1, 0}}; int[][] result = matrixPow(unitMatrix, n); return result[0][0]; } public int[][] matrixPow(int[][] mat, int n) { if (n == 1) { return mat; } else { // n是偶数 if ((n & 1) == 0) { int[][] temp = matrixPow(mat, n >> 1); return matrixMultiply(temp, temp); } else { // n是奇数 int[][] temp = matrixPow(mat, (n - 1) >> 1); return matrixMultiply(matrixMultiply(temp, temp), mat); } } } /** * 矩阵相乘 * * @param m * @param n * @return 结果矩阵,m*n */ public int[][] matrixMultiply(int[][] m, int[][] n) { int rows = m.length; int cols = n[0].length; int[][] r = new int[rows][cols]; for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { r[i][j] = 0; for (int k = 0; k < m[i].length; k++) { r[i][j] += m[i][k] * n[k][j]; } } } return r; } public static void main(String args[]) { Solution s = new Solution(); int result = s.fib(3); System.out.println(result); } }
排列组合实现
排列组合是将跳1级数目和跳2级数目看出一个组合,其中x为跳1级数目,y为跳2级数目,且x+2y=n,问题转换成从(x+y)个总跳数中选出y个跳2级的的组合,即C(x+y,y)=C(n-y,y)。实现代码如下:
public class Solution { public BigDecimal jiecheng(int number) { BigDecimal result = new BigDecimal(1); BigDecimal temp; for (int i = number; i > 0; i--) { temp = new BigDecimal(i); result = result.multiply(temp); } return result; } public int fib(int target) { if (target <= 0) { return 0; } BigDecimal result = new BigDecimal(0); // the result is C(n-y,y) for (int i = 0; i <= target / 2; i++) { result = result.add(jiecheng(target - i).divide(jiecheng(target - 2 * i).multiply(jiecheng(i)))); } return result.toBigInteger().intValue(); } public static void main(String args[]) { Solution s = new Solution(); int result = s.fib(29); System.out.println(result); } }