递归是在一个函数的内部调用这个函数自身,而循环是通过设置计算的初始值及终止条件,在一个范围内重复运算。
递归代码比较简洁,但也存在缺点,因为函数调用是有时间和空间的消耗的:每一次函数调用,都需要在内存中分配空间以保存参数、返回地址及临时变量,而往栈里压入数据和弹出数据都需要时间,且可能会造成调用栈溢出。
题目一:写一个函数,输入n,求斐波拉契数列的第n项,斐波拉契数列定义如下
f(n)=0,n=0 f(n)=1,n=1 f(n)=f(n-1)+f(n-2),n>1
思路:可以使用递归,但当n非常大时不适用,用循环解决
public class wr9Fibonacci { // 递归 public static long fibonacci(int n){ if(n<=0){ return 0; } if(n==1){ return 1; } return fibonacci(n-1)+fibonacci(n-2); } // 循环,时间复杂度为O(n) public static long fibonacci2(int n){ long fn1=1; long fn2=1; if(n<=0){ return 0; } if(n==1 || n==2){ return 1; } for(;n>2;n--){ long temp=fn1; fn1=fn1+fn2; fn2=temp; // 不设置临时变量 // fn1=fn1+fn2; // fn2=fn1-fn2; } return fn1; } public static void main(String[] args) { // TODO Auto-generated method stub System.out.println(fibonacci(10)); System.out.println(fibonacci2(10)); } }
题目二:一只青蛙一次可以跳上1级台阶,也可以跳上2级,求该青蛙跳上一个n级台阶总共有多少种跳法
思路:如果只有1级,只有一种跳法;如果2级,有两种跳法,即一次跳2级,分两次跳每次跳1级,分析后发现实际就是斐波拉契数列
f(n)=1,n=1 f(n)=2,n=2 f(n)=f(n-1)+f(n-2),n>2
public static int jumpFloor(int target){ int fn2=1; int fn1=2; if(target<=0){ return 0; } if(target==1){ return 1; } if(target==2){ return 2; } for(;target>2;target--){ fn1=fn1+fn2; fn2=fn1-fn2; } return fn1; }
题目三:一只青蛙一次可以跳上1级台阶,也可以跳上2级,,,也可以跳上n级,求该青蛙跳上一个n级台阶总共有多少种跳法
思路:n级台阶,第一步有n种跳法:跳1级、跳2级,,,跳n级
跳1级,剩下n-1级,则剩下跳法是f(n-1)
跳2级,剩下n-2级,则剩下跳法是f(n-2)
所以f(n)=f(n-1)+f(n-2)+...+f(1)
因为f(n-1)=f(n-2)+f(n-3)+...+f(1)
所以f(n)=2*f(n-1)
所以还是斐波拉契数列
f(n)=0,n=0 f(n)=1,n=1 f(n)=2*f(n-1),n>=2
public int jumpFloorMore(int target){ if(target<=0){ return 0; } int count=1; for(;target>1;target--){ count=count*2; } return count; }也可以用数学归纳法证明计算公式,代入直接计算
f(n)=2^(n-1)
扩展:我们可以用2*1的小矩形横着或者竖着去覆盖更大的矩形,问:用8个2*1的小矩形无重叠覆盖一个2*8的大矩形,共有多少种方法
思路:2*8的覆盖方法记为f(8),用第一个2*1小矩形去覆盖大矩形的最左边有两个选择,竖着或横着。竖着时,右边方法为f(7),横着时,右边为f(6),所以f(8)=f(7)+f(6)
public static int juxing(int target){ if(target<=0){ return 0; } if(target==1){ return 1; } if(target==2){ return 2; } int fn1=1; int fn2=2; for(;target>2;target--){ fn2=fn2+fn1; fn1=fn2-fn1; } return fn2; }总结:在遇到题目时,有时候我们可以通过数学逻辑推导找出规律,要善于发现,善于思考