斐波那契数列系列算法最优复杂度——时间复杂度优化到O(LogN)
对于菲薄那契系列问题的探讨很多,下面就以两个例子来分析:
-
案例 一:
在迷迷糊糊的大草原上,小红捡到了n根木棍,第i根木棍的长度为i, 小红现在很开心。想选出其中的三根木棍组成美丽的三角形。 但是小明想捉弄小红,想去掉一些木棍,使得小红任意选三根木棍都不能组成 三角形。 请问小明最少去掉多少根木棍呢? 给定N,返回至少去掉多少根?
比如N=14.那么木棍的长度分别是1 2 3 4 5 6 7 8 9 10 11 12 13 14
由于三角形条件必须满足 两边之和大于第三边
所以我们只要保留木棍长度为 1 2 3 5 8 13的木棍。这些木棍任意组合都无法构成三角形
观察这个数列,就是满足F(n) = F(n-1)+F(n-2) 的公式 ,这就是纯粹的斐波那契数列问题 -
案例二(斐波那契相关的探讨,也可以优化到O(logN):
一个农场,有一个母牛,农场通过生育技术人工授精让母牛只能生母牛而不让生公牛。母牛从第二年开 始生母牛,对于新生的牛需要等过了三年才能生母牛
假设牛不会死,问n年后农场有多少只母牛?
第一年 :母牛A 1只
第二年: 母牛A生B 2只
第三年: 母牛A生C,A,B 3只
第四年:母牛A生D,A,B,C 4只
第五年:母牛A生E,B生F,A,B,C,D 6只
第六年:母牛A生G,B生H,C生I,A,B,C,D,E,F 9只
上面的多少只的数量满足公式 F(N) = F(N-1)+F(N-3)
对于案例一无非就是求出斐波那契数列问题了:
相信大家都对斐波那契数列已经相当的熟悉了,最多两分钟就可以写出来以下时间复杂度为O(N)的代码:
//递归实现
long long fib(int n)
{
if (n =1 || n== 2)
{
return 1;
}
return (fib(n - 2) + fib(n - 1));
}
但是上面的实现的时间复杂度是O(N)面试的时候是没有分的,而我想说的是优化到O(logN),那么下面就来探讨如何让时间复杂度到O(logN):
(由于编辑器实在难敲出数学特殊符号所以我拍成几张图片如下:)
下面就是实现代码:
public static void main(String[] args) {
System.out.println(fibonacci(5));
System.out.println(fibonacci2(5));
}
/**
* 普通解法时间复杂度O(N)
* @param n
* @return
*/
public static int fibonacci(int n) {
if(n<1){
return 0;
}
if(n==1|| n==2){
return 1;
}else{
return fibonacci(n-1)+fibonacci(n-2);
}
}
/**
* 整个调用时间复杂度O(logN)
* @param n
* @return
*/
public static int fibonacci2(int n){
if(n<1){
return 0;
}
if(n==1 || n==2){
return 1;
}
int[][] base = new int[][]{
{1,1},
{1,0}
};
int[][] res = marixPow(base,n-2);
return res[0][0]+res[1][0];
}
private static int[][] marixPow(int[][] base, int n) {
int[][] res = new int[base.length][base[0].length];
for(int i=0;i<res.length;i++){
res[i][i] = 1;//单位矩阵
}
int[][] t = base;
for(;n!=0;n>>=1){
if((n&1)!=0){//满足指数bit为为1情况
res = multMaritx(res,t);
}
//基数矩阵在不断变化
t = multMaritx(t,t);
}
return res;
}
/**
* 虽然是三个for循环但是矩阵就是个2*2的大小有限。所以实际时间复杂度O(1)
* @param t
* @param t1
* @return
*/
private static int[][] multMaritx(int[][] t, int[][] t1) {
int[][] res = new int[t.length][t1[0].length];
for(int i=0;i<t.length;i++){
for(int j=0;j<t1[0].length;j++){
for(int k=0;k<t1.length;k++){
res[i][j] += t[i][k]*t1[k][j];
}
}
}
return res;
}
下面来分析案例二:
由于案例二的公式是:F(N) = F(N-1)+F(N-3)
这是个3X3的矩阵,我们也可以通过带入前面几个值求出矩阵base来:
以下是案例二的实现代码最优解:O(logN):
/**
* @author zhanglijie
* @version 1.0
* @since 1.1.0 2021/5/7 0007 20:54
* 一个农场,有一个母牛,农场通过生育技术人工授精让母牛只能生母牛而不让生公牛。母牛从第二年开始生母牛,对于新生的牛需要等过了三年才能生母牛
* 假设牛不会死,问n年后农场有多少只母牛?
*
* 第一年 :母牛A 1只
* 第二年: 母牛A生B 2只
* 第三年: 母牛A生C,A,B 3只
* 第四年:母牛A生D,A,B,C 4只
* 第五年:母牛A生E,B生F,A,B,C,D 6只
* 第六年:母牛A生G,B生H,C生I,A,B,C,D,E,F 9只
* 13
* 19
*
*
* 公式:F(N) = F(N-1)+F(N-3)
*
*/
public class CowHowMany {
public static void main(String[] args) {
int year =8;
int sum = getHowManyCow(year);
System.out.println(sum);
}
private static int getHowManyCow(int year) {
if(year<=3){
return year;
}
int[][] base = new int[][]{
{1,1,0},
{0,0,1},
{1,0,0}
};
int[][]res = martixPow(base,year-3);
return 3*res[0][0]+2*res[1][0]+1*res[2][0];
}
private static int[][] martixPow(int[][] base, int n) {
int[][] res = new int[base.length][base[0].length];
for(int i=0;i<res.length;i++){
res[i][i] = 1;//单位矩阵
}
int[][] t = base;
for(;n!=0;n>>=1){
if((n&1)!=0){
res = multMaritx(res,t);
}
t = multMaritx(t,t);
}
return res;
}
private static int[][] multMaritx(int[][] t, int[][] t1) {
int[][] res = new int[t.length][t1[0].length];
for(int i=0;i<t.length;i++){
for(int j=0;j<t1[0].length;j++){
for(int k=0;k<t1.length;k++){
res[i][j] += t[i][k]*t1[k][j];
}
}
}
return res;
}
}
有关斐波那契套路的算法很多很多,我们在刷leetcode的时候或者在面试笔试的时候都会见到,以上是我对其的总结。