剑指Offer--009-斐波那契数列
来源:互联网 发布:光纤宽带连接不上网络 编辑:程序博客网 时间:2024/04/29 08:05
链接
牛客OJ:斐波那契数列
九度OJ:http://ac.jobdu.com/problem.php?pid=1387
GitHub代码: 009-斐波那契数列
CSDN题解:剑指Offer–009-斐波那契数列
您也可以选择回到目录-剑指Offer–题集目录索引
参考:
剑指Offer面试题9(java版)斐波那契数列
O(logn)时间复杂度求Fibonacci数列
剑指offer第9题及扩展 斐波那契数列
题意
题目描述
大家都知道斐波那契数列,
现在要求输入一个整数n,
请你输出斐波那契数列的第n项。
递推公式
我们很容易的想到了递推公式
*
*
*
因此我们马上想到了如下递归代码
int Fibonacci(int n) { if(n <= 1) { return n; } else { return Fibonacci(n - 1) + Fibonacci(n - 2); } }
但是很抱歉StackOverFlow了。
事实上,用递归的方法计算的时间复杂度是以n的指数的方式递增的。读者不妨求Fibonacci的第100项..
递归展开–迭代方法O(N)
因此我们只能用最普通的方法,将递推公式进行展开
#include <iostream>using namespace std;// 调试开关#define __tmain main#ifdef __tmain#define debug cout#else#define debug 0 && cout#endif // __tmainclass Solution{public: int Fibonacci(int n) { if(n <= 1) { return n; } long one = 0; long two = 1;; long res = 0; for(int i = 2; i <= n; i++) { res = one + two; one = two; two = res; } return res; }};int __tmain( ){ Solution solu; cout <<solu.Fibonacci(3) <<endl;; return 0;}
这个算法这还不是最快的方法。下面介绍一种时间复杂度是O(logn)的方法。
时间复杂度是O(logn)的但是不够实用的算法
在介绍这种方法之前,先介绍一个数学公式:
{f(n), f(n-1), f(n-1), f(n-2)} ={1, 1, 1,0}n-1
(注:{f(n+1), f(n), f(n), f(n-1)}表示一个矩阵。在矩阵中第一行第一列是f(n+1),第一行第二列是f(n),第二行第一列是f(n),第二行第二列是f(n-1)。)
有了这个公式,要求得f(n),我们只需要求得矩阵{1, 1, 1,0}的n-1次方,因为矩阵{1, 1, 1,0}的n-1次方的结果的第一行第一列就是f(n)。这个数学公式用数学归纳法不难证明。感兴趣的朋友不妨自己证明一下。
现在的问题转换为求矩阵{1, 1, 1, 0}的乘方。如果简单第从0开始循环,n次方将需要n次运算,并不比前面的方法要快。但我们可以考虑乘方的如下性质:
/ an/2*an/2 n为偶数时an= \ a(n-1)/2*a(n-1)/2 n为奇数时
要求得n次方,我们先求得n/2次方,再把n/2的结果平方一下。如果把求n次方的问题看成一个大问题,把求n/2看成一个较小的问题。这种把大问题分解成一个或多个小问题的思路我们称之为分治法。这样求n次方就只需要logn次运算了。
实现这种方式时,首先需要定义一个2×2的矩阵,并且定义好矩阵的乘法以及乘方运算。当这些运算定义好了之后,剩下的事情就变得非常简单。完整的实现代码如下所示。
原理:
观察: a(n) = a(n - 1) + a(n - 2)
= 2 * a(n - 2) + a (n - 3)
= 3 * a(n - 3) + 2 * a(n - 4)
= 5 * a(n - 4) + 3 * a(n - 5)
= … …
= a(k) * a(n - k + 1) + a(k - 1) * a(n - k)
1、若令 n = 2k
得 a(2k) = a(k) * a(k + 1) + a(k-1) * a(k)
= a(k) * [a(k) + a(k - 1)] + a(k-1) * a(k)
= a(k) ^ 2 + 2 * a(k) * a(k - 1)
2、若令 n = 2k - 1
得 a(2k - 1) = a(k) * a(k) + a(k - 1) * a(k - 1)
= a(k) ^ 2 + a(k - 1) ^ 2
代码如下
#include <iostream>using namespace std;// 调试开关#define __tmain main#ifdef __tmain#define debug cout#else#define debug 0 && cout#endif // __tmainclass Solution{public : static int pre; static int post; int temp; int Fibonacci(int n) { /** * 每次Fibonacci调用结束后: * pre: 存的都是Fibonacci(n)的值 * post: 存的都是Fibonacci(n-1)的值 */ // if n = 0 #=> 0 // else n = 1 || n = 2 #=> 1 if(n <= 2) { if (n == 0) return 0; pre = 1; post = 1; return pre; } // n 为奇数, 则做减一操作, 函数回调时 // pre: f(n) = f(n - 1) + f(n - 2) // post: f(n - 1) = f(n) - f(n - 2) if(n%2 == 1) { Fibonacci(n-1); pre = pre + post; post = pre - post; return pre; } // n为偶数时不断除二 // 函数回调时,f(n) 通过 f(n/2) 来计算: // // 设 n = 2k: // f(2k) = f(k) * f(k + 1) + f(k-1) * f(k) // = f(k) * [f(k) + f(k - 1)] + f(k-1) * f(k) // = f(k) ^ 2 + 2 * f(k) * f(k - 1) // 设 n = 2k-1: // f(2k - 1) = f(k) * f(k) + f(k - 1) * f(k - 1) // = f(k)^2 + f(k - 1)^2 // Fibonacci(n/2); temp = pre; // f(2k) = f(k)^2 + 2 * f(k) * f(k - 1) pre = pre * pre + 2 * pre * post; // f(2k - 1) = f(k) * f(k) + f(k - 1) * f(k - 1) post = temp*temp + post*post; return pre; }};int Solution::pre = 0;int Solution::post = 0;int __tmain( ){ Solution solu; cout <<solu.Fibonacci(3) <<endl;; return 0;}
扩展一–跳台阶
题目描述
一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
分析
每次只能跳一级或者跳两级,那么调N个台阶就可以分解为
先跳1级,然后剩余的是跳一个
N−1 级的台阶,先跳2级,然后剩余的是跳一个
N−2 级的台阶,
那么它的递推公式为
=0,当n=0 =1,当n=1 =2,当n=2 =f(n−1)+f(n−2),其他
代码
#include <iostream>using namespace std;// 调试开关#define __tmain main#ifdef __tmain#define debug cout#else#define debug 0 && cout#endif // __tmainclass Solution{public: int jumpFloor(int n) { if(n <= 2) { return n; } long one = 1; long two = 2;; long res = 0; for(int i = 3; i <= n; i++) { res = one + two; one = two; two = res; } return res; }};int __tmain( ){ Solution solu; cout <<solu.jumpFloor(2) <<endl;; return 0;}
扩展二–变态跳台阶
题目描述
一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
分析
关于本题,前提是n个台阶会有一次n阶的跳法。分析如下:
f(1) = 1
f(2) = f(2-1) + f(2-2) //f(2-2) 表示2阶一次跳2阶的次数。
f(3) = f(3-1) + f(3-2) + f(3-3)
…
f(n) = f(n-1) + f(n-2) + f(n-3) + … + f(n-(n-1)) + f(n-n)
说明:
这里的f(n) 代表的是n个台阶有一次1,2,…n阶的 跳法数。
n = 1时,只有1种跳法,f(1) = 1
n = 2时,会有两个跳得方式,一次1阶或者2阶,这回归到了问题(1) ,f(2) = f(2-1) + f(2-2)
n = 3时,会有三种跳得方式,1阶、2阶、3阶,
那么就是第一次跳出1阶后面剩下:f(3-1);第一次跳出2阶,剩下f(3-2);第一次3阶,那么剩下f(3-3)
因此结论是f(3) = f(3-1)+f(3-2)+f(3-3)n = n时,会有n中跳的方式,1阶、2阶…n阶,得出结论:
f(n) = f(n-1)+f(n-2)+…+f(n-(n-1)) + f(n-n) => f(0) + f(1) + f(2) + f(3) + … + f(n-1)由以上已经是一种结论,但是为了简单,我们可以继续简化:
f(n-1) = f(0) + f(1)+f(2)+f(3) + … + f((n-1)-1) = f(0) + f(1) + f(2) + f(3) + … + f(n-2)
f(n) = f(0) + f(1) + f(2) + f(3) + … + f(n-2) + f(n-1) = f(n-1) + f(n-1)
可以得出:f(n)=2∗f(n−1) 得出最终结论,在n阶台阶,一次有1、2、…n阶的跳的方式时,总得跳法为:
1 ,(n=0 )
1 ,(n=1 )
2*f(n-1),(n>=2)
代码
#include <iostream>using namespace std;// 调试开关#define __tmain main#ifdef __tmain#define debug cout#else#define debug 0 && cout#endif // __tmainclass Solution{public : int jumpFloorII(int target) { if (target <= 0) { return -1; } else if (target == 1) { return 1; } else { return 2 * jumpFloorII(target - 1); } }};int __tmain( ){ Solution solu; cout <<solu.jumpFloorII(2) <<endl;; return 0;}
或者
class Solution{public : int jumpFloorII(int target) { if (target <= 0) { return -1; } else { return pow(2, target - 1); } }};
或者更高效的
#include <iostream>using namespace std;// 调试开关#define __tmain main#ifdef __tmain#define debug cout#else#define debug 0 && cout#endif // __tmainclass Solution{public : int jumpFloorII(int target) { long long ret; if(target >= 32)//大于8字节后,需要分两次移位,否则出错 { ret = 1 << 30; ret = ret << (target - 31); } else { ret = 1 << (target - 1); } return ret; }};int __tmain( ){ Solution solu; cout <<solu.jumpFloorII(2) <<endl;; return 0;}
扩展三–矩形覆盖
题目描述
我们可以用2*1的小矩形横着或者竖着去覆盖更大的矩形。请问用n个2*1的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?
分析
2*n的矩形方法数定义为f(n).
第一个2*1的小矩形覆盖大矩形的左边,
要么竖着放,转化成f(n-1),
要么横着放两次,转化成f(n-2),
因此f(n) = f(n-1) + f(n-2)
1 n = 0
1 n = 1
f(n - 1) + f(n - 2)
代码
#include <iostream>using namespace std;// 调试开关#define __tmain main#ifdef __tmain#define debug cout#else#define debug 0 && cout#endif // __tmainclass Solution{public: int rectCover(int n) { if(n == 0) { return 1; } else if(n == 1) { return 1; } long one = 1; long two = 1; long res = 0; for(int i = 2; i <= n; i++) { res = one + two; one = two; two = res; } return res; }};int __tmain( ){ Solution solu; cout <<solu.rectCover(2) <<endl;; return 0;}
- 【剑指offer】斐波那契数列
- 剑指offer--斐波那契数列
- 剑指Offer-斐波那契数列
- 剑指offer:斐波那契数列
- 《剑指offer》斐波那契数列
- 剑指 offer:斐波那契数列
- 【剑指Offer】斐波那契数列
- 剑指offer 斐波那契数列
- 【剑指offer】斐波那契数列
- 剑指offer 斐波那契数列
- [剑指offer]斐波那契数列
- 剑指offer|斐波那契数列
- 【剑指offer】斐波那契数列
- 剑指offer--斐波那契数列
- 剑指Offer:斐波那契数列
- 《剑指Offer》斐波那契数列
- 剑指offer----斐波那契数列
- 剑指offer-斐波那契数列
- 模拟用户登录
- 动态规划---最大子段和&最大子矩阵和
- 笔记练习:《Javascript入门经典(第5版)》page150_12.8_Practice
- ArrayList集合中 迭代 加元素
- java 方法的覆盖
- 剑指Offer--009-斐波那契数列
- STL set/multiset 常用函数
- 干货--Redis池化联接和搭建分布式集群
- TreeSet字典顺序排序
- 录入5名学生成绩 按总分排序
- LeetCode 54 Spiral Matrix
- SurfaceFlinger GraphicBuffer内存共享缓冲区机制
- OC阅读笔记十五:"异常安全代码"的内存管理
- ubuntu增加磁盘