《剑指offer》纪念版:目录索引
问题:写一个函数,输入n,求斐波那契数列的第n项。斐波那切数列的定义如下:
一般算法:递归(时间复杂度O(2^n))
long long FiboRecursion(unsigned int n)
{
if(n == 0)
return 0;
else if(n == 1)
return 1;
else
return FiboRecursion(n-1) + FiboRecursion(n-2);
}
递归解法写起来很简单,但是效率极差,因为它在不停的重复计算,根据上图就可以看出重复计算步骤太多,并且随着n的增加重复的结点也会急剧增加。因此我们需要更好的算法。
进阶算法:循环(时间复杂度O(n))
long long Fibonacci(unsigned int n)
{
long long first = 0;
long long second = 1;
long long FibN = 0;
if(n == 0)
return 0;
else if(n == 1)
return 1;
else
{
while(n-1)
{
FibN = first + second;
first = second;
second = FibN;
n--;
}
}
return FibN;
}
从前向后计算,避免重复计算。
高阶算法:矩阵(时间复杂度O(logn))
用这个算法需要知道这个公式
我们计算第k个斐波那切数列,即计算f(k),
则要计算(k-1)阶矩阵
f(k)值就是(k-1)阶矩阵左上角的值,就是在程序中返回的值arr._arr00。
个人对公式推导理解如下:
但是如果只是从0开始循环,进行矩阵n次方运算它的时间复杂度还是O(n),并没有比循环方法好在哪里。因此我们需要借助下面这个公式
从上面公式看出,求n次方,转换成先求n/2次方;求n/2次方,转换成(n/2)/2次方,依次递归下去,等递归到n=1时,返回结果再平方。
Fib.h
/////////////////////////////////
#ifndef _FIB_F
#define _FIB_F
#include<stdio.h>
#include <windows.h>
typedef struct FibArr
{
long long _arr00;
long long _arr01;
long long _arr10;
long long _arr11;
}FibArr;
FibArr ArrSquare(FibArr arr1,FibArr arr2);//矩阵平方
FibArr FibLogN(size_t n);//奇偶区分
long long FibN(size_t n);//求n的斐波那切数列
#endif //_FIB_F
Fib.c
//////////////////////////////////////////
#define _CRT_SECURE_NO_WARNINGS 1
#include"Fib.h"
FibArr Arr = {1,1,1,0};
FibArr ArrSquare(FibArr arr1,FibArr arr2)//矩阵平方
{
FibArr arr;
arr._arr00 = arr1._arr00*arr2._arr00 + arr1._arr01*arr2._arr10;
arr._arr01 = arr1._arr00*arr2._arr01 + arr1._arr01*arr2._arr11;
arr._arr10 = arr1._arr10*arr2._arr00 + arr1._arr11*arr2._arr10;
arr._arr11 = arr1._arr10*arr2._arr01 + arr1._arr11*arr2._arr11;
return arr;
}
FibArr FibLogN(size_t n)//奇偶区分
{
FibArr arr;
if(1 == n)//递归终止条件
{
return Arr;
}
else if (n % 2 == 0)
{
arr = FibLogN(n/2);
arr = ArrSquare(arr,arr);
}
else if(n % 2 == 1)
{
arr = FibLogN((n - 1)/2);
arr = ArrSquare(arr,arr);
arr = ArrSquare(arr,Arr);
}
return arr;
}
long long FibN(size_t n)//求n的斐波那切数列
{
FibArr arr;
if(n < 2)
{
return n;
}
arr = FibLogN(n-1);
return arr._arr00;
}
test.c
///
#define _CRT_SECURE_NO_WARNINGS 1
#include"Fib.h"
Test3()//测试矩阵
{
printf("%d ",FibN(0));
printf("%d ",FibN(1));
printf("%d ",FibN(2));
printf("%d ",FibN(3));
printf("%d ",FibN(4));
printf("%d ",FibN(5));
printf("%d ",FibN(6));
printf("%d ",FibN(7));
printf("%d ",FibN(8));
printf("%d ",FibN(9));
printf("%d\n",FibN(10));
}
int main()
{
//Test1();
//Test2();
Test3();
system("pause");
return 0;
}
结果:
扩展题一:
一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶,求该青蛙跳上一个n级的台阶总共有多少种跳法。
分析:一级台阶有一种跳法,二级台阶有两种跳法,一种一次跳两级,另一种跳两次,一次跳一级,如果是n级台阶,第一次跳一级,则后面就有f(n-1)级台阶跳法的数目,第一次跳二级,则后面有f(n-2)级台阶跳法数目,因此n阶台阶转换为f(n)=f(n-1)+f(n-2)菲波那切数列问题了。
long long FioFrogJumpR(size_t n)//斐波那切青蛙跳问题递归
{
if(n <= 2)
{
return n;
}
else return FioFrogJumpR(n-1)+FioFrogJumpR(n-2);
}
long long FioFrogJump(size_t n)//斐波那切青蛙跳问题
{
size_t count = n -2;
long long first = 1;
long long second = 2;
long long FibN = 0;
if(n <= 2)
return n;
else
{
while(count--)
{
FibN = first + second;
first = second;
second = FibN;
}
}
return FibN;
}
扩展题二:
一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶… … 它也可以跳上n级台阶,求该青蛙跳上一个n级的台阶总共有多少种跳法。
分析:
long long FioFrogJumpN(size_t n)//斐波那切青蛙跳N级问题-非递归
{
long long FibN = 0;
if(n == 0)
{
return 0;
}
else
{
FibN = (long long)pow(2,n-1);
}
return FibN;
}
long long FioFrogJumpNR(size_t n)//斐波那切青蛙跳N级问题-递归
{
if(n == 0)
{
return 0;
}
else if(n == 1)
{
return 1;
}
return 2*FioFrogJumpNR(n - 1);
}
扩展题三:
我们可以用下图(左边2x1)的小矩形横着或者竖着去覆盖更大的矩形。请问用8个2x1的小矩形无重叠地覆盖在一个2x8的大矩形(下图右边),总共有多少种方法?
分析:
从左边开始覆盖,第一次覆盖有两种选择,横着放或者竖着放
此时横着放又转换成2×6的大矩形被覆盖,竖着放转换成2×7的大矩形被覆盖问题,即f(8) =f(7)+f(6),依然是斐波那切数列问题。
f(7)=f(6)+f(5)
f(6)=f(5)+f(4)
……
long long FioRecCoverR(size_t n)//矩形覆盖-递归
{
if(n <= 2)
{
return n;
}
return FioRecCoverR(n-1)+FioRecCoverR(n-2);
}
long long FioRecCover(size_t n)//矩形覆盖-循环
{
size_t count = n - 2;
long long Fibfirst = 1;
long long Fibsecod = 2;
long long FibN = 0;
if (n <= 2)
{
return n;
}
else
{
while(count--)
{
FibN = Fibsecod + Fibfirst;
Fibfirst = Fibsecod;
Fibsecod = FibN;
}
}
return FibN;
}