一. 原问题
在数学上,斐波纳契数列以如下被以递归的方法定义:F(0)=0,F(1)=1, F(n)=F(n-1)+F(n-2)(n>=2,n∈N*),即从第三项开始,每一项等于前两项之和。
输入格式:输入包括一行,包括一个整数N (0≤N≤50)。
输出格式:输出包括一行,包括一个整数,为斐波那契数列的第 N 项的值。
递归方法
时间复杂度为O(2^n)。import java.util.Scanner; public class Fibonacci_1 { public static void main(String[] args) { Scanner input = new Scanner(System.in); int n = input.nextInt(); long f = fib(n); System.out.println(f); } public static long fib(int n) { if(n == 0){ return 0; }else if (n == 1){ return 1; }else { return fib(n - 1) + fib(n - 2); } } }
数组存储,动态规划
当一个问题可以分解成若干重复的子问题时,运用动态规划的思想:只需要将子问题求解一次,以后再遇到,直接调用,所以新建一个数组用于存储子问题的结果。(以空间换时间)
import java.util.Scanner; public class Fibonacci_2 { static Scanner input = new Scanner(System.in); static int n = input.nextInt(); static long[] result = new long[n + 1]; public static void main(String[] args) { long f = fib(n); System.out.println(f); } public static long fib(int n) { long f = 0; if(n == 0){ f = 0; }else if (n == 1){ f = 1; }else if (result[n] > 0){ return result[n]; }else { f = fib(n - 1) + fib(n - 2); } result[n] = f; return f; } }
递推方法
时间复杂度为O(n)import java.util.Scanner; public class Fibonacci_3 { public static void main(String[] args) { long f0 = 0; long f1 = 1; long f = 0; Scanner input = new Scanner(System.in); int n = input.nextInt(); if(n == 0){ f = 0; }else if(n == 1){ f = 1; }else { for(int i = 2; i <= n; i++){ f = f0 + f1; f0 = f1; f1 = f; } } System.out.println(f); } }
矩阵相乘算法
推导过程:
由此演变成了求此矩阵的(n - 1)次方的过程。
import java.util.Scanner; public class Fibonacci_4{ public static void main(String[] args) { Scanner input = new Scanner(System.in); int n = input.nextInt(); long f = fib(n); System.out.println(f); } public static long[][] baseMartrixMulti(int n) { long[][] result = {{1, 0}, {0, 1}};//单位阵 long[][] base = {{1, 1}, {1, 0}}; long[][] temp = {{1, 0}, {0, 1}}; for (int i = 0; i < n; i++){ result[0][0] = temp[0][0] * base[0][0] + temp[0][1] * base[1][0]; result[0][1] = temp[0][0] * base[0][1] + temp[0][1] * base[1][1]; result[1][0] = temp[1][0] * base[0][0] + temp[1][1] * base[1][0]; result[1][1] = temp[1][0] * base[0][1] + temp[1][1] * base[1][1]; temp[0][0] = result[0][0];//特别注意,数组赋值赋的是引用,不能直接temp = result; temp[0][1] = result[0][1]; temp[1][0] = result[1][0]; temp[1][1] = result[1][1]; } return result; } public static long fib(int n) { long f = 0; if(n != 0){ long[][] trans = baseMartrixMulti(n - 1); f = trans[0][0];//意义为下两条注释的语句 //long[][] bas = {{1}, {0}}; //f = trans[0][0] * bas[0][0] + trans[0][1] * bas[1][0]; } return f; } }
矩阵相乘和乘方可以单独抽象出来函数,此处只是为了解决固定矩阵的乘方问题,没有给出普适代码,若有需要参考 矩阵乘法求斐波那契数列 。
此过程中发现乘方优化算法,参考 乘方的优化算法,可结合到矩阵乘方,待研究后贴出代码。
公式法
万能的数学家得出斐波那契数列通项公式如上,推导过程略(其实是因为看不懂,有兴趣自行百度 orz)。又称为“比内公式”,是用无理数表示有理数的一个范例。
代码实现如下:
import java.util.Scanner; public class Fibonacci_5 { public static void main(String[] args) { Scanner input = new Scanner(System.in); int n = input.nextInt(); long f = (long)((Math.pow((1 + Math.sqrt(5)) / 2, n) - Math.pow((1 - Math.sqrt(5)) / 2, n)) / Math.sqrt(5)); System.out.println(f); } }
二. 引申问题
爬楼梯问题
假设你现在正在爬楼梯,楼梯有 n 级。每次你只能爬 1 级或者 2 级,那么你有多少种方法爬到楼梯的顶部?
输入格式:第一行输入一个整数 n(1≤n≤50),代表楼梯的级数。
输出格式:输出爬到楼梯顶部的方法总数。
其实相当于斐波那契问题,只是初始条件改变。f(1) = 1, f(2) = 2, n > 2时,f(n) = f(n - 1) + f(n - 2) 。 (若用矩阵运算可以将base矩阵减少一阶,也可以虚拟假设 f(0) = 1)
任选一种以上方法给出代码:
import java.util.Scanner; public class Climb { public static void main(String[] args) { long f0 = 1; long f1 = 1; long f = 0; Scanner input = new Scanner(System.in); int n = input.nextInt(); if(n == 1){ f = 1; }else{ for(int i = 2; i <= n; i++){ f = f0 + f1; f0 = f1; f1 = f; } } System.out.println(f); } }
疯狂跳台阶
(题目来源于http://blog.youkuaiyun.com/xingyanxiao/article/details/47055973)一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
因此,。f(1)=1,f(2)=2。
代码略。
兔子问题
已知第一个月有一对刚出生的兔子,从出生第三个月起,一对兔子每个月可以生一对小兔子。假如没有发生死亡,则第一个月开始,第N个月后会有多少对?
这里以对为单位,从第一个月开始,每个月总共的兔子数量就是1, 1, 2, 3, 5, 8, 13……可以看出前两个月为1,从第三个月开始,当月的数量为前两个月数量之和,所以 f(n) = f(n - 1) + f(n - 2)(n > 2)。同时f(1) = 1, f(2) = 1。
代码实现同标准数学问题。
类似斐波那契问题还有很多,注意递推公式的推导即可。