1.题目描述I
一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)
1.1 解题思路
这和斐波那契数列是同一个思想,可以倒过来看:我要走完n层,我有两种方式,在n-1层走1步或者n-2层走2步。依次往下推导可得到这样一个表达式:
f(n) = f(n-1)+f(n-2);
但是作为递归,需要一个出口:
f(1) = 1; f(2) = 2;
1.2 code: java
//递归最复杂的方式,但题目限制了我们改进
public static int JumpFloor(int target) {
if(target == 1 || target == 2)
return target;
else return JumpFloor(target-1)+JumpFloor(target-2);
}
//也有用递推的思想
int jumpFloor(int target) {
int t1=1,t2=2,total=0;
if (target==1||target==2) return target;
for(int i=3;i<=target;i++) {
total=t1+t2;
t1=t2;
t2=total;
}
return total;
}
1.3 分析:
上述代码都能过,但是有一个很大的问题。都有重复计算的过程,如果题目传的参数改变,换成数组,那么第二种里面把for循环里的改成数组存储,那也能做到优化。
下面我自己试着改变题目条件,重写一遍,用剪枝的思想减少时间复杂度。
通过画图先分析之前的算法:
图1 常规递归运算过程
可以明显地看到有很多是重复的,这样运算的复杂度当然会很大,所以对递归的题目,如果我们有了存储,那复杂度会大大降低,这个方法是通过数组方式来存储和标记,具体计算的过程就是下图2了:
图2 改进递归的计算过程
1.4 code:java改进
第一种:数组跟踪标记
package 剑指offer;
public class jumpFloorII {
static int[] arr= new int[50];
static int[] boolean_arr = new int[50];
public static int JumpFloorII(int target,int[] arr,int[] boolean_arr) {
/**
* 以下程序1和2需要互换位置,以免JumpFloorII(2)重复计算
*/
//程序①
if(target==1 || target==2){
arr[target] = target;
boolean_arr[target]++;
return target;
}
//程序②
if(boolean_arr[target]!=0) {
return arr[target];
}
else{
arr[target] = JumpFloorII(target-1,arr,boolean_arr)+JumpFloorII(target-2,arr,boolean_arr);
boolean_arr[target]++;
}
return arr[target];
}
public static void main(String[] args) {
int target=10;
System.out.println(JumpFloorII(target,arr,boolean_arr));
for(int i=1;i<=target;i++) {
System.out.println("arr"+"["+i+"]"+"="+arr[i]);
System.out.println("boolean_arr"+"["+i+"]"+"="+boolean_arr[i]);
}
}
}
/*我通过打印boolean_arr数组,来观察其是否经过重复运算,1则表示只进行一次,所以这样看来,实现了我们所要的效果
89
arr[1]=1
boolean_arr[1]=1
arr[2]=2
boolean_arr[2]=2
arr[3]=3
boolean_arr[3]=1
arr[4]=5
boolean_arr[4]=1
arr[5]=8
boolean_arr[5]=1
arr[6]=13
boolean_arr[6]=1
arr[7]=21
boolean_arr[7]=1
arr[8]=34
boolean_arr[8]=1
arr[9]=55
boolean_arr[9]=1
arr[10]=89
boolean_arr[10]=1
*/
但是应该都能注意到为什么boolean_arr【2】=2,这是因为在target=4的时候,它分出了f(3)和f(2),一边f(3)去递归,得到f(2)和f(1),这里boolean_arr【2】++,加了一次,一边f(2)右加了一次,因为是遇到f(2)直接返回的,所以boolean_arr【2】=2。为了规避这个问题,只需要把
程序①和程序②调换一下位置即可。
//第二种:更方便简洁
public static int JumpFloorII(int target,int[] arr) {
arr[1]=1;
arr[2]=2;
for(int i=3;i<=target;i++){
arr[i]=arr[i-1]+arr[i-2];
}
return arr[target];
}
2 知识延伸
① 对于上述的第二种,传入的数组也可以用C++的vector,其本质也是数组。
② 遇到数据量特别大的,可以考虑用java的BigInteger来做。
参考:https://blog.youkuaiyun.com/kazama_kenji/article/details/52330338
3 题目描述II
一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
3.1 解题思路
寻找规律,有了上面那题的引导,我们继续循着逆向思考,从N开始,直接一步算一种,N-1->N,需要f(n-1)种,N-2需要f(n-2)种,注意,对于这题,跳的步数不再是1步或者2步了,而是n步。所以需要继续加上f(n-3)……
所以递归公式就出来了:
f(n) = 1+f(n-1)+f(n-2)+……f(1);
递归出口:
f(1)=1; f(2)=2;
3.2 code: java
public class Solution {
public int JumpFloorII(int target) {
int tot=0;
if(target==1 || target==1){
return target;
}
else for(int i=1;i<=target-1;i++) {
tot += JumpFloorII(i);
}
return tot+1;
}
}
4 题目描述-矩形覆盖
我们可以用2*1的小矩形横着或者竖着去覆盖更大的矩形。请问用n个2*1的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?
4.1 解题思路:一样,先找规律,再找出口
4.2 code java
public class Solution {
public int RectCover(int target) {
if(target == 0)
return 0;
if(target == 1 ||target == 2)
return target;
return RectCover(target-1)+RectCover(target-2);
}
}