剑指offer-10-斐波那契数列

《剑指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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值