剑指Offer——跳台阶

时间:2024-11-13 13:06:56

题目描述

一只青蛙一次可以跳上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),该算法是根据下面的公式:

剑指Offer——跳台阶

上面的公式可以数学归纳法证明,感兴趣的可以自己搜索一下相关资料,有了上面的公式,就将求解f(n),转换成求二阶矩阵的n次方,然后取结果的第1行第2列即可。如果仅仅是矩阵的乘法,时间复杂度依旧为O(n),但可以使用快速幂的方法求解一个数的乘法。乘方的性质如下:

剑指Offer——跳台阶

结合上面两个公式代码如下:

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);
} }