题目: 一只青蛙一次可以跳上 1 级台阶,也可以跳上2 级。求该青蛙跳上一个n 级的台阶总共有多少种跳法。
题目分析:
- 如下图所示,当只有一阶台阶时,青蛙只有一种跳法。
- 当只有两阶台阶时,青蛙有两种算法,也就是一次跳一个或者直接跳两个。
- 但当有三阶台阶时,我们此时:
(1)可以先选择跳1阶,这时第1阶的台阶就看做是新的起点,这时我们可以发现剩2阶台阶了,前面我们说了两阶台阶有两种跳法。
(2)同样,也可以先选择跳2阶,这时第2阶的台阶就看做是新的起点,这时我们可以发现剩1阶台阶了,前面我们说了1阶台阶有1种跳法。
(3)所以我们可以得出,当有3阶台阶时,共有2+1种跳法(也就是前面2阶台阶跳法结果 + 前面1阶台阶跳法结果)。 - 同理,当有四阶台阶时,我们此时:
(1)可以先选择跳1阶,这时第1阶的台阶就看做是新的起点,这时我们可以发现剩3阶台阶了,前面我们说了3阶台阶有3种跳法。
(2)同样,也可以先选择跳2阶,这时第2阶的台阶就看做是新的起点,这时我们可以发现剩2阶台阶了,前面我们说了2阶台阶有2种跳法。
(3)所以我们可以得出,当有3阶台阶时,共有3+2种跳法(也就是前面3阶台阶跳法结果 + 前面2阶台阶跳法结果)。
总结: 说白了上面过程其实就是用前面的结果,来快速得到下一步的结果。
分析完题目我们就可以写代码了,实现代码如下所示:
方法一:递归实现
public class test2{
public static int jump(int n) {
if(n == 1){
return 1;
}else if(n == 2){
return 2;
}else{
return jump(n-1) + jump(n-2);
}
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
System.out.println(jump(n));
}
}
或
import java.util.*;
public class Solution {
public int jumpFloor (int number) {
if(number <=1 ){
return 1;
}else{
return jumpFloor(number-1) +jumpFloor(number-2);
}
}
public static void main(String[] args){
Scanner scanner = new Scanner(System.in);
int a = scanner.nextInt();
System.out.print(a);
}
}
优点:代码简单好写。
缺点:慢,会超时,且费空间。
时间复杂度:O(2^n) ,每个递归会调用两个递归,因此呈现2的指数增长。
空间复杂度:O(n) 递归栈的空间,栈空间最大深度为n。
方法二:递归改进实现(用数组或map)
我们会发现方法一中,存在很多重复计算,比较浪费时间,因此为了改进,就把计算过的保存下来。 那么用什么保存呢?一般会想到map或数组。
但这里要说一下,不建议采用数组,因为我们不确定台阶的数量,无法预先申请确定大小的数组。数组实现代码如下所示:
class Solution {
public:
int f[50]{0};
int jumpFloor(int number) {
if (number <= 1) return 1;
if (f[number] > 0) return f[number];
return f[number] = (jumpFloor(number-1)+jumpFloor(number-2));
}
};
或
public class Solution {
//设置全局变量,因为实际问题中没有0,则可用0作初始标识值
private int[] dp = new int[50];
public int F(int n){
if(n <= 1)
return 1;
//若是dp中有值则不需要重新递归加一次
if(dp[n] != 0)
return dp[n];
//若是dp中没有值则需要重新递归加一次
return dp[n] = F(n - 1) + F(n - 2);
}
public int jumpFloor(int target) {
return F(target);
}
}
时间复杂度:O(n) ,每个数字只算了一次。
空间复杂度:O(n) ,栈空间最大深度。
map实现代码如下所示:
class Solution {
public:
map<int,int> map_flag;//记录已经被计算出来的值
int jumpFloor(int number) {
if(number<=1) return 1;//初始两个台阶的结果
if(map_flag.count(number)){//已经被计算过;采用count的函数可以降低内存与时间;
return map_flag[number];
}
return map_flag[number]=jumpFloor(number-1)+jumpFloor(number-2);//未被计算过,存入map;
}
};
时间复杂度:O(n) ,每个数字只算了一次。
空间复杂度:O(n) ,栈空间最大深度。
方法三:非递归实现(动态规划)
本题既然与斐波那契数列相同,我们就把它放入数组中来解决。
public class test2{
public static void main(String[] args) {
int n = 5;
int[] array = new int[n];
array[0] = 1;
array[1] = 2;
for (int i = 2; i < n; i++) {
array[i]= array[i-1] + array[i-2];
System.out.println(i+1+"阶"+array[i]+"种跳法");
}
}
}
时间复杂度:O(n) ,遍历了一次长度为n的数组。
空间复杂度:O(n) ,建立了一个数组辅助。
方法四:数组做空间复杂度是o(n),能不能空间复杂度为o(1)呢?(动态规划)
动态规划算法的基本思想是: 将待求解的问题分解成若干个相互联系的子问题,先求解子问题,然后从这些子问题的解得到原问题的解;对于重复出现的子问题,只在第一次遇到的时候对它进行求解,并把答案保存起来,让以后再次遇到时直接引用答案,不必重新求解。动态规划算法将问题的解决方案视为一系列决策的结果。本解法属于动态规划空间优化方法:
继续优化: 发现计算array[4]的时候只用到了array[3]和array[2], 没有用到array[1]和array[0],所以保存array[1]和array[0]是浪费了空间。 只需要设置2个变量定义即可。
时间复杂度:O(n)、空间复杂度:O(1) ,完美!