斐波拉契数列、楼梯问题、奶牛问题

斐波拉契数列:波那契数列,又称黄金分割数列,指的是这样一个数列:0、1、1、2、3、5、8、13、21、34、……在数学上,斐波纳契数列以如下被以递归的方法定义:F(0)=0,F(1)=1,F(n)=F(n-1)+F(n-2)(n≥2,n∈N*)[from 百度百科 http://baike.baidu.com/link?url=8LKtKTAllUGDMe610zIO0DAjS3CCeAOpXiCFvH_Y47_I_XDRgzyGcrzsodd1OHO726FJNPWkqzkQC7PIuGu_I_

问题1:

对于斐波拉契经典问题,我们都非常熟悉,通过递推公式F(n) = F(n - 1) + F(n -  2),我们可以在线性时间内求出第n项F(n),现在考虑斐波拉契的加强版,我们要求的项数n的范围为int范围内的非负整数,请设计一个高效算法,计算第n项F(n)。第一个斐波拉契数为F(0)  = 1。给定一个非负整数,请返回斐波拉契数列的第n项,为了防止溢出,请将结果Mod 1000000007。[from牛客网 ]

方法1:

最基础的方法是最简单的递归,会产生大量的重复运算,时间复杂度太大,不予推荐。

方法2:

经过推导发现:

当n为偶数时 F(n) =  F(n/2)^2 + F(n/2 - 1)^2

当n为奇数时 F(n) =  F(n/2)^2 + 2*F(n/2)*F(n/2 - 1)

由此可采用:

int Fibonacci(int n)
{
    long i, j;
        if (n == 0 || n == 1)
            return 1;
        i = Fibonacci(n/2);
        j = Fibonacci(n/2 - 1);
        if (n%2 == 0)
            return (i*i + j*j)%1000000007;
        else 
            return (2*i*j+ i*i)%1000000007;
}

此算法类似与二分查找,假如方法1的时间复杂度为N,方法2的时间复杂度可能为log2 N(此处不确定)

虽然这种方法有所改善,但是仍不理想,重复计算过多。

注:此方法的i*i部分是大整数之间的乘法,数量巨大,消耗大量时间。

方法3:

斐波拉契经典问题还可以通过矩阵的方法(具体方法此处不列举)来解,有方法2相比,算是一种空间换时间的折中。

由于斐波拉契矩阵大致为a[2][2]={1,1,1,0},矩阵大整数间的运算有所减少,相比方法2也是一种提升。

对于n为偶数或为奇数的问题:

为避免大整数间乘法,根据矩阵乘法规律:

当m为偶数时:arr(m/2, a);, 再求矩阵a*a

当m为奇数时:arr(m/2, a);,再求矩阵a*a*{1 , 1, 1, 0}

/*
矩阵处理函数
*/
void arr(int m, long long (*d)[2])
        {
            long long a[2][2];
            long long e[2][2];
            long long c[2][2] = {1, 1, 1, 0};
            if (m == 1) {
                d[0][0] = 1;
                d[0][1] = 1;
                d[1][0] = 1;
                d[1][1] = 0;
                return;
            }
            arr(m/2, a);
            e[0][0] = (a[0][0]*a[0][0] + a[0][1]*a[1][0])%1000000007;
            e[0][1] = (a[0][0]*a[0][1] + a[0][1]*a[1][1])%1000000007;
            e[1][0] = (a[1][0]*a[0][0] + a[1][1]*a[1][0])%1000000007;
            e[1][1] = (a[1][0]*a[0][1] + a[1][1]*a[1][1])%1000000007;
            if (m%2 != 0) {
                d[0][0] = (e[0][0]*c[0][0] + e[0][1]*c[1][0])%1000000007;
                d[0][1] = (e[0][0]*c[0][1] + e[0][1]*c[1][1])%1000000007;
                d[1][0] = (e[1][0]*c[0][0] + e[1][1]*c[1][0])%1000000007;
                d[1][1] = (e[1][0]*c[0][1] + e[1][1]*c[1][1])%1000000007;
            }
            else {
            d[0][0] = e[0][0];
            d[0][1] = e[0][1];
            d[1][0] = e[1][0];
            d[1][1] = e[1][1];
            }
        }
/*
调用函数
*/
int Fibonacci(int n) {
        // write code here
        long long b[2][2] = {1, 1, 1, 0};
        if (n == 0 || n == 1)
            return 1;
        arr(n - 1, b);
        return (b[0][0] + b[0][1])%1000000007;
}

问题2:

现在有一栋高楼,但是电梯却出了故障,无奈的你只能走楼梯上楼,根据你的腿长,你一次能走1级或2级楼梯,已知你要走n级楼梯才能走到你的目的楼层,请计算你走到目的楼层的方案数,由于楼很高,所以n的范围为int范围内的正整数。

给定楼梯总数n,请返回方案数。为了防止溢出,请返回结果Mod 1000000007的值。[from 牛客网 ]

分析:

一个人一次只能走一个台阶,或者两个台阶。那么到台阶N总共有两种大方式,从N-1上来(N-1 > 0),从N-2(N-2 > 0)上来。那么到达台阶N的方式即到达N-1的方式加上到达N-2的方式,即F(n) = F(n-1) + F(n-2)。那么该问题即是斐波拉契数列问题,方法完全一样。

问题3:

在农场中,奶牛家族是一个非常庞大的家族,对于家族中的母牛,从它出生那年算起,第三年便能成熟,成熟的母牛每年可以生出一只小母牛。即母牛从出生 开始的第三年便能做妈妈。最开始农场只有一只母牛,它从第二年开始生小母牛。请设计一个高效算法,返回第n年的母牛总数,已知n的范围为int范围内的正 整数。

给定一个正整数n,请返回第n年的母牛总数,为了防止溢出,请将结果Mod 1000000007。[from 牛客网 ]

分析:

奶牛分为成熟和未成熟两种,成熟每年产子一枚,未成熟需三年成熟后才能产子。

将奶牛分为三类,成熟,出生一年,出生两年

第N年奶牛总数的 = 三种奶牛数想加

第N年 成熟的奶牛 = 第N-1年成熟的奶牛 + 第N-1年出生两年的奶牛

第N年 出生一年的奶牛 = 第N-1年成熟的奶牛

第N年 出生两年的奶牛 = 第N-1年出生一年的奶牛 = 第N-2年成熟的奶牛

那么:

全用成熟的奶牛代替出生一年或两年的奶牛:

第N年 成熟的奶牛 = 第N-1年成熟的奶牛 + 第N-3年成熟的奶牛

F(n) = F(n-1) + F(n-3)

第N年 出生一年的奶牛 = 第N-1年成熟的奶牛

第N年 出生两年的奶牛 = 第N-2年成熟的奶牛

第N年 奶牛总数 = 第N年成熟 + 第N-1年成熟的奶牛 + 第N-2年成熟的奶牛

此时得到了,奶牛总数和成熟奶牛之间的关系

由矩阵乘法得

1    0    1            F(n-1)            F(n-1) + F(n-3)              F(n)

1    0    0     X     F(n-2)     =    F(n-1)                    =      F(n-1)

0    1    0            F(n-3)            F(n-2)                             F(n-2)

此时,经过矩阵乘法,F(n) 里的n顺利向后递增,则矩阵可满足此算法,最后的奶牛总数为:F(n) + F(n - 1) + F(n - 2)

此算法采用3*3矩阵,其他与问题2相似:

void arr(int m, long long (*d)[3])
        {
            long long a[3][3];
            long long e[3][3];
            long long c[3][3] = {1, 0, 1, 1, 0, 0, 0, 1, 0};
            if (m == 1) {
                d[0][0] = 1;
                d[0][1] = 0;
                d[0][2] = 1;
                d[1][0] = 1;
                d[1][1] = 0;
                d[1][2] = 0;
                d[2][0] = 0;
                d[2][1] = 1;
                d[2][2] = 0;
                return;
            }
            arr(m/2, a);
            e[0][0] = (a[0][0]*a[0][0] + a[0][1]*a[1][0] + a[0][2]*a[2][0])%1000000007;
            e[0][1] = (a[0][0]*a[0][1] + a[0][1]*a[1][1] + a[0][2]*a[2][1])%1000000007;
            e[0][2] = (a[0][0]*a[0][2] + a[0][1]*a[1][2] + a[0][2]*a[2][2])%1000000007;
            e[1][0] = (a[1][0]*a[0][0] + a[1][1]*a[1][0] + a[1][2]*a[2][0])%1000000007;
               e[1][1] = (a[1][0]*a[0][1] + a[1][1]*a[1][1] + a[1][2]*a[2][1])%1000000007;
            e[1][2] = (a[1][0]*a[0][2] + a[1][1]*a[1][2] + a[1][2]*a[2][2])%1000000007;
            e[2][0] = (a[2][0]*a[0][0] + a[2][1]*a[1][0] + a[2][2]*a[2][0])%1000000007;
            e[2][1] = (a[2][0]*a[0][1] + a[2][1]*a[1][1] + a[2][2]*a[2][1])%1000000007;
            e[2][2] = (a[2][0]*a[0][2] + a[2][1]*a[1][2] + a[2][2]*a[2][2])%1000000007;
            if (m%2 != 0) {
                d[0][0] = (e[0][0]*c[0][0] + e[0][1]*c[1][0] + e[0][2]*c[2][0])%1000000007;
                d[0][1] = (e[0][0]*c[0][1] + e[0][1]*c[1][1] + e[0][2]*c[2][1])%1000000007;
                d[0][2] = (e[0][0]*c[0][2] + e[0][1]*c[1][2] + e[0][2]*c[2][2])%1000000007;
            
            d[1][0] = (e[1][0]*c[0][0] + e[1][1]*c[1][0] + e[1][2]*c[2][0])%1000000007;
            d[1][1] = (e[1][0]*c[0][1] + e[1][1]*c[1][1] + e[1][2]*c[2][1])%1000000007;
            d[1][2] = (e[1][0]*c[0][2] + e[1][1]*c[1][2] + e[1][2]*c[2][2])%1000000007;
            
            d[2][0] = (e[2][0]*c[0][0] + e[2][1]*c[1][0] + e[2][2]*c[2][0])%1000000007;
                d[2][1] = (e[2][0]*c[0][1] + e[2][1]*c[1][1] + e[2][2]*c[2][1])%1000000007;
                d[2][2] = (e[2][0]*c[0][2] + e[2][1]*c[1][2] + e[2][2]*c[2][2])%1000000007;
            }
            else {
                d[0][0] = e[0][0];
                d[0][1] = e[0][1];
                d[0][2] = e[0][2];
            
            d[1][0] = e[1][0];
            d[1][1] = e[1][1];
            d[1][2] = e[1][2];
            
            d[2][0] = e[2][0];
               d[2][1] = e[2][1];
                d[2][2] = e[2][2];
            }
        }
    int countSum(int n) {
        // write code here
        long long b[3][3] = {1, 0, 1, 1, 0, 0, 0, 1, 0};
        int e[3] = {1, 1, 1}; //F(3) F(2) F(1)
        long long f[3];
        //前三个为满足条件直接输出
        if (n == 1)
            return 1;
        if (n == 2)
            return 2;
        if (n == 3 )
            return 3;
        arr(n - 3 , b);
        f[0] = b[0][0]*e[0] + b[0][1]*e[1] + b[0][2]*e[2];
        f[1] = b[1][0]*e[0] + b[1][1]*e[1] + b[1][2]*e[2];
        f[2] = b[2][0]*e[0] + b[2][1]*e[1] + b[2][2]*e[2];
        return (f[0] + f[1] + f[2]) %1000000007;
    }

总结:

奶牛问题与楼梯问题看似关系不大,最后都能转化为矩阵解法

矩阵能很好的简化多余运算,实现空间转时间。

时间: 2024-10-25 05:01:16

斐波拉契数列、楼梯问题、奶牛问题的相关文章

斐波拉契数列应用

斐波拉契数列的应用实例 什么是斐波拉契数列(Fibonacci sequence)?将其前几项写出来就是:0 1 1 2 3 5 8 13 21....... 观察不难发现其规律是,从第二项起,每一项的值都为前两项的和.而且这个数列有趣的地方就在于这个非常特殊的规律.它是有通项公式的,但是推导与主题无关,而且也几乎用不上,所以就不多叙述. long fi(int n) { if(n==1||n==2) return 1; else return fi(n-1)+fi(n-2); } 但是如果这样

Fibonacci斐波拉契数列----------动态规划DP

n==10 20 30 40 50 46 体验一下,感受一下,运行时间 #include <stdio.h>int fib(int n){ if (n<=1)     return 1; else            return fib(n-1)+fib(n-2); }int main( ){ int n; scanf("%d",&n); printf("%d\n" ,fib(n) );} 先 n==10 20 30 40 50 46

青蛙跳台阶问题-斐波拉契数列

题目1:一个台阶总共有n级,如果一次可以跳1级,也可以跳2级.求总共有多少种跳法 首先我们考虑最简单的情况,加入只有1级台阶,那显然只有一种跳法,如果有2级台阶,那就有两种跳的方法了:一种是分两次跳,每次跳1级:另外一种就是一次跳2级 现在我们来讨论一般情况.我们把n级台阶时的跳法看成是n的函数,记为f(n).当n>2时,第一次跳的时候就有两种不同的选择:一是第一次只跳1级,此时跳法数目等于后面剩下的n-1级台阶的跳法数目,即为f(n-1):另外一种选择是第一次跳2级,此时跳法数目等于后面剩下的

浅谈C#中的斐波拉契数列

突然对那些有趣的数学类知识感兴趣了,然后就简单研究了一下斐波拉契数列,看看它的有趣之处! 斐波拉契数列(Fibonacci Sequence),又称黄金分割数列,该数列由意大利的数学家列奥纳多·斐波那契发现的.这种数列指的是这样一个数列:0.1.1.2.3.5.8.13.21. 34.--在数学上,斐波纳契数列以如下被以递归的方法定义:F(0)=0,F(1)=1,F(n)=F(n-1)+F(n-2)(n≥2,n∈N*). 用C#实现斐波拉契数列的代码: Console.Write("请输入一个长

在c#中编写斐波拉契数列程序

思路:首先因为输出的是一个数列,又因为不定长,所以要见一个集合来装数列,其次确定第一个数和第二个数都为1,然后根据斐波拉契数列的特点,确定是一个循环语句,再根据从第三位开始,每个数字都是前两个数的和的特点写出代码.代码如下: while(true){Console.Write("请输入斐波拉契数列的长度:");int len = int.Parse(Console.ReadLine());int[] array = new int[len];if (len < 3){Consol

斐波拉契数列的计算方法

面试题9.斐波拉契数列 题目: 输入整数n,求斐波拉契数列第n个数. 思路: 一.递归式算法: 利用f(n) = f(n-1) + f(n-2)的特性来进行递归,代码如下: 代码: long long Fib(unsigned int n) { if(n<=0) return 0; if(n==1) return 1; return Fib(n-1) + Fib(n-2); } 缺陷: 当n比较大时递归非常慢,因为递归过程中存在很多重复计算. 二.改进思路: 应该采用非递归算法,保存之前的计算结

斐波拉契数列加强版——时间复杂度O(1),空间复杂度O(1)

对于斐波拉契经典问题,我们都非常熟悉,通过递推公式F(n) = F(n - 1) + F(n - 2),我们可以在线性时间内求出第n项F(n),现在考虑斐波拉契的加强版,我们要求的项数n的范围为int范围内的非负整数,请设计一个高效算法,计算第n项F(n).第一个斐波拉契数为F(0) = 1. 给定一个非负整数,请返回斐波拉契数列的第n项,为了防止溢出,请将结果Mod 1000000007. 斐波拉契数列的计算是一个非常经典的问题,对于小规模的n,很容易用递归的方式来获取,对于稍微大一点的n,为

斐波拉契数列问题

古典问题:有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第三个月后每个月又生一对兔子,假如兔子都不死,问每个月的兔子总数为多少? package Test; /** * 斐波拉契数列问题(兔子问题) * 可推导递推公式 * f(n+1)=f(n)+f(n-1) * */ public class FibonacciNumeral { public static void main(String[] args) { System.out.println("第一个月的兔子为1"

c语言:写一个函数,输入n,求斐波拉契数列的第n项(5种方法,层层优化)

写一个函数,输入n,求斐波拉契数列的第n项. 斐波拉契数列:1,1,2,3,5,8...,当n大于等于3时,后一项为前面两项之和. 解:方法1:从斐波拉契数列的函数定义角度编程 #include<stdio.h> int fibonacci(int n) { int num1=1, num2=1, num3=0,i; if (n <= 2) { printf("斐波拉契数列的第%d项为:%d\n",n,num1); } else { for (i = 2; i <