更多题目请点链接:《剑指offer》目录索引
问题描述:
写入一个函数,输入n,求斐波那契数列的第n项,斐波那契数列的定义如下:
思路:
三种算法:递归,非递归,矩阵
时间复杂度:递归O(2^n),非递归O(n),矩阵O(logn)
算法比较:递归算法效率低,重复计算,容易溢出;非递归算法避免重复计算,比较实用;矩阵算法效率高,但包含生僻的数学算法,不实用
递归算法:
long long Fib(unsigned int n)
{
if(n<2)
return n;
return Fib(n-1)+Fib(n-2);
}
非递归算法:
long long FibNonR(unsigned int n)
{
long long first=1,second=1,result=1;
if(n==0)
return 0;
while (n > 2)
{
result = first + second;
first = second;
second = result;
n--;
}
return result;
}
矩阵算法:
首先看一个数学公式,根据这个公式,
我们计算f(n)时,即计算
即可得到f(n),证明如下:
![]()
根据以上数学的证明,可知求f(n)相当于n-1个矩阵相乘;但此运算的时间复杂度为O(n),故引入一个公式:
从上面公式可看出,n次方转化为n/2次方,n/2次方又转化为n/2/2次方…….. 这样的算法时间复杂度为O(log n)
代码:
Fib.h
#pragma once
#include<stdio.h>
#include<windows.h>
typedef struct FibArr
{
long long _a00;
long long _a01;
long long _a10;
long long _a11;
}FibArr;
FibArr MulArr(FibArr arr1, FibArr arr2);
FibArr FibOfLogN(size_t n);
long long FibFn(size_t n);
void Test();
Fib.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "Fib.h"
FibArr ARR = { 1, 1, 1, 0 };
FibArr MulArr(FibArr arr1, FibArr arr2)
{
//矩阵乘法
FibArr arr;
arr._a00 = arr1._a00*arr2._a00 + arr1._a01*arr2._a10;
arr._a01 = arr1._a00*arr2._a01 + arr1._a01*arr2._a11;
arr._a10 = arr1._a10*arr2._a00 + arr1._a11*arr2._a10;
arr._a11 = arr1._a10*arr2._a01 + arr1._a11*arr2._a11;
return arr;
}
FibArr FibOfLogN(size_t n)
{
FibArr arr;
if (n == 1)
{
arr = ARR;
}
else if (n % 2 == 0)
{
//偶数,(A1^(n/2))*(A1^(n/2))
arr = FibOfLogN(n / 2);
arr = MulArr(arr, arr);
}
else if (n % 2 == 1)
{
//奇数,(A1^(n/2)) *(A1^(n/2))* A1
arr = FibOfLogN((n - 1) / 2);
arr = MulArr(arr, arr);
arr = MulArr(arr, ARR);
}
return arr;
}
long long FibFn(size_t n)
{
if (n < 2)
{
return n;
}
FibArr arr = FibOfLogN(n-1);
return arr._a00;
}
void Test()
{
printf("%d ", FibFn(0));
printf("%d ", FibFn(1));
printf("%d ", FibFn(2));
printf("%d ", FibFn(3));
printf("%d ", FibFn(4));
printf("%d ", FibFn(5));
printf("%d ", FibFn(6));
printf("%d ", FibFn(10));
printf("%d ", FibFn(30));
printf("\n");
}
test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"Fib.h"
int main()
{
Test();
system("pause");
return 0;
}
结果:
扩展一:
一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶,求该青蛙跳上一个n级的台阶总共有多少种跳法。
思路:
递归子问题:
每跳1级台阶,剩下n-1级,此时求n-1级台阶共有多少种跳法
每次跳台阶时,都要选择跳1级还是2级,直至跳完n级台阶为止
这种选择的跳法实际上就是斐波那契数列的问题
代码:
size_t Jump(size_t n)//递归
{
if(n<=1)
return 1;
if(n==2)
return 2;
return Jump(n-1)+Jump(n-2);
}
size_t FibNonR(size_t n)//非递归
{
size_t first=1,second=1,result=1;
if(n<=1)
return 1;
if(n==2)
return 2;
while (n > 2)
{
result = first + second;
first = second;
second = result;
n--;
}
return result;
}
扩展二:
一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶… … 它也可以跳上n级台阶,求该青蛙跳上一个n级的台阶总共有多少种跳法。
思路:
1.归纳法:
n=0,0种跳法;
n=1,1种跳法;
n=2,2种跳法;
n=3,4种跳法;
………….
n级台阶,2^(n-1)种方法;
代码:
//归纳法
size_t JumpPow(size_t n)
{
if(n == 0)
{
return 0;
}
else
{
return pow(2,n-1);
}
}
2.递归法:
0级台阶,没有台阶跳,f(0)=0
1级台阶,只有一级台阶,f(1)=1
2级台阶,可以一次跳1级台阶,一次跳2级台阶,f(2)=f(2-1)+f(2-2)
3级台阶,可以一次跳1级台阶,一次跳2级台阶,在跳一级,或者一次跳3级台阶,f(3)=f(3-1)+f(3-2)+f(3-3)
……………..共有n-1级台阶时,f(n-1)=f(n-2)+f(n-3)+…….f(1)+f(0)
共有n级台阶时,f(n)=f(n-1)+f(n-2)+f(n-3)+………..+f(1)+f(0),即n>=2时,f(n)=2*f(n-1)
总结为
代码:
//递归
size_t JumpR(size_t n)
{
if(n == 0)
return 0;
if(n == 1)
return 1;
return 2*JumpR(n-1);
}
扩展三:
我们可以用2x1的小矩形横着或者竖着去覆盖更大的矩形。请问用8个2x1的小矩形无重叠地覆盖在一个2x8的大矩形,总共有多少种方法?
思路:
从左向右开始覆盖,第一次有两种选择,横着放和竖着放
根据上图,我们可将其转化为斐波那契数列数列问题
f(8)=f(7)+f(6)
f(7)=f(6)+f(5)
f(6=f(5)+f(4)
………..
代码:
//递归
long long CoverR(long long n)
{
if(n <= 2 )
{
return n;
}
return Cover(n-1)+Cover(n-2);
}
//非递归
long long CoverNonR(long long n)
{
long long first=1,second=1,result=1;
if(n<=1)
return 1;
if(n==2)
return 2;
while (n > 2)
{
result = first + second;
first = second;
second = result;
n--;
}
return result;
}