题解 | #Fibonacci sSum#

Fibonacci sSum

http://www.nowcoder.com/practice/2c212c21da5e44c1a1a0543d975f4d1e

描述

这是一篇面对初级coder的题解。

知识点:数学 递推

难度:五星


题解

题目: 求斐波那契数列前n项和的前n项和的前n项和。

分析: 斐波那契数列本身就有一定的递推特性,需要结合数学知识递推求得

方法一 递推:

已知f(n)=f(n1)+f(n2)f(n)=f(n-1)+f(n-2)f(n)=f(n1)+f(n2)

T(n)=i=1nf(i)T(n)=\sum_{i=1}^nf(i)T(n)=i=1nf(i)T(n)=T(n1)+f(n)T(n)=T(n-1)+f(n)T(n)=T(n1)+f(n)

G(n)=i=1nT(i)G(n)=\sum_{i=1}^nT(i)G(n)=i=1nT(i)G(n)=G(n1)+T(n)G(n)=G(n-1)+T(n)G(n)=G(n1)+T(n)

F(n)=i=1nG(i)F(n)=\sum_{i=1}^nG(i)F(n)=i=1nG(i)F(n)=F(n1)+G(n)F(n)=F(n-1)+G(n)F(n)=F(n1)+G(n)

F(n)F(n)F(n) 即为所求 这样的话就优化了递推的式子。

class Solution {
public:
    const int mod=1e9+7;
    int getSum(int n) {
        /*初始值*/
        int f[3]={0,1,1};//斐波那契数列
        int T=1;//f前n项和T
        int G=1;//T前n项和G
        int F=1;//G前n项和F——所求
        for(int i=2;i<=n;i++){
            f[2]=(f[1]+f[0])%mod;//f(n)=f(n-1)+f(n-2)=1
            f[0]=f[1];//f(n-2)=f(n-1)
            f[1]=f[2];//f(n-1)=f(n)
            T=(T+f[2])%mod;//T(n)=T(n-1)+f(n)
            G=(G+T)%mod;//G(n)=G(n-1)+T(n)
            F=(F+G)%mod;//F(n)=F(n-1)+G(n)
        }
        return F;
    }
};

运行测试:

9/10 组用例通过 运行时间 1001ms 占用内存 432KB

复杂度分析:

采用递归和记忆化搜索的方式,避免了4重循环

故时间复杂度为O(n)O(n)O(n)——只遍历一遍 空间复杂度为O(1) 仅占用常数空间

方法二 通项公式 递推:

斐波那契数列前n项和:

考虑如下式子

f(n+2)=f(n+1)+f(n)f(n+2)=f(n+1)+f(n)f(n+2)=f(n+1)+f(n)

f(n+1)=f(n)+f(n1)f(n+1)=f(n)+f(n-1)f(n+1)=f(n)+f(n1)

f(n)=f(n1)+f(n2)f(n)=f(n-1)+f(n-2)f(n)=f(n1)+f(n2)

....

f(3)=f(2)+f(1)f(3)=f(2)+f(1)f(3)=f(2)+f(1)

对于上面所有式子累加求和有 f(n+2)=i=1nf(i)+f(1)f(n+2)=\sum_{i=1}^nf(i)+f(1)f(n+2)=i=1nf(i)+f(1)

T(n)=i=1nf(i)=f(n+2)1 T(n)=\sum_{i=1}^nf(i) = f(n+2)-1T(n)=i=1nf(i)=f(n+2)1

同样的G(n)=i=1nT(i)=T(n+2)T(2)n=f(n+4)f(4)n=f(n+4)(n+3) G(n)=\sum_{i=1}^nT(i) = T(n+2)-T(2)-n=f(n+4)-f(4)-n=f(n+4)-(n+3)G(n)=i=1nT(i)=T(n+2)T(2)n=f(n+4)f(4)n=f(n+4)(n+3)

依然类似 F(n)=i=1nG(i)=T(n+4)T(4)i=4nn+3=f(n+6)f(6)(n+7)n/2 F(n)=\sum_{i=1}^nG(i) =T(n+4)-T(4)-\sum_{i=4}^nn+3=f(n+6)-f(6)-(n+7)*n/2F(n)=i=1nG(i)=T(n+4)T(4)i=4nn+3=f(n+6)f(6)(n+7)n/2

最后一项是等差数列求和公式且f(6)=8f(6)=8f(6)=8

故所求F(n)=f(n+6)8(n+7)n/2F(n)=f(n+6)-8-(n+7)*n/2F(n)=f(n+6)8(n+7)n/2

通过这样的计算 减少了递推的次数 问题也划归为求斐波那契数列的第n+6项

class Solution {
public:
    const int mod=1e9+7;
    int getSum(int n) {
        /*初始值*/
        int f[3]={0,1,1};//斐波那契数列
        for(int i=2;i<=n+6;i++){
            f[2]=(f[1]+f[0])%mod;//f(n)=f(n-1)+f(n-2)=1
            f[0]=f[1];//f(n-2)=f(n-1)
            f[1]=f[2];//f(n-1)=f(n)
        }
        long long ans=(mod+f[2]-8-(long long)(n+7)*(long long)n/2%mod)%mod;
        return (int) ans;
    }
};

运行测试:

9/10 组用例通过 运行时间 1001ms 占用内存 432KB

复杂度分析:

采用递归和记忆化搜索的方式,避免了4重循环

故时间复杂度为O(n)O(n)O(n)——只遍历一遍

空间复杂度为O(1) 仅占用常数空间

但是仍然超时 说明递推求斐波那契数列的第n+6项仍然耗费过多时间,需要用二分法做进一步优化。

方法三 通项公式 二分法递推:

考虑用数学归纳法证明如下两个关于斐波那契数列的性质

性质一:

若i为奇数, f(i)f(i)=f(i1)f(i+1)+1f(i)*f(i)=f(i-1)*f(i+1)+1f(i)f(i)=f(i1)f(i+1)+1,否则f(i)f(i)=f(i1)f(i+1)1f(i)*f(i)=f(i-1)*f(i+1)-1 f(i)f(i)=f(i1)f(i+1)1

性质二:

f(n)=f(1)f(n2)+f(2)f(n1)=f(2)f(n3)+f(3)f(n2)=f(3)f(n4)+f(4)f(n3)=f(i)f(ni1)+f(i+1)f(ni)f(n)=f(1)*f(n-2)+ f(2)*f(n-1) \\ =f(2)*f(n-3)+ f(3)*f(n-2) \\ =f(3)*f(n-4)+ f(4)*f(n-3) \\ =f(i)*f(n-i-1)+f(i+1)*f(n-i)f(n)=f(1)f(n2)+f(2)f(n1)=f(2)f(n3)+f(3)f(n2)=f(3)f(n4)+f(4)f(n3)=f(i)f(ni1)+f(i+1)f(ni)

i=n/2i=n/2i=n/2,可采用二分法用来计算较大的fibonacci数除以某个数的余数,以乘法代替加法,从而将递推复杂度变为O(logn)O(logn)O(logn)

#define max 1000000//测试这个值比较合适 时间短
class Solution {
public:
    const int mod=1e9+7;
    int Fib(int n,vector<int> &f){//二分法计算前n项和
        long long ans;//返回值
        if(n<=max)
            return f[n];//分情况 二分太细乘法复杂度会上升 故先打表
        else{
            int i=n/2;
            //核心二分公式 详见解析 f(n)=f(i)*f(n-i-1)+f(i+1)*f(n-i)
            ans=((long long) Fib(i,f)*(long long) Fib(n-i-1,f)+(long long)Fib(i+1,f)*(long long)Fib(n-i,f))%mod;
        }
        return (int) ans;
    }
    int getSum(int n) {
        vector<int> f(max);//斐波那契数列
        /*初始值*/
        f[0]=0;
        f[1]=1;
        for(int i=2;i<max;i++)//预打表
            f[i]=(f[i-1]+f[i-2])%mod;//f(n)=f(n-1)+f(n-2)=1
        long long ans=(mod+Fib(n+6,f)-8-(long long)(n+7)*(long long)n/2%mod)%mod;//推导公式直接计算
        return (int) ans;
    }
};

运行测试:

通过全部用例 运行时间 21ms 占用内存 4296KB

复杂度分析:

实际测试发现,频繁做小数的乘法耗时长,故定义一个分界值max

小于此值时用递推的结果,大于此值用二分的结果

故时间复杂度为O(logn)O(logn)O(logn)——二分法递推

空间复杂度为O(1) 仅占用常数空间

总结

数学很重要

当n进一步增大时 即n+6>modn+6>modn+6>mod

考虑斐波那契数列通项公式 alt

与费马小定理

alt

f(n+6)%mod=f(n+6mod)%modf(n+6) \% mod=f(n+6-mod) \% modf(n+6)%mod=f(n+6mod)%mod

阿Q的题解 文章被收录于专栏

阿Q秋招刷过的题

全部评论

相关推荐

02-12 20:22
重庆大学 Java
字节暑期刚入职四天,因为是年前,所以很多正职都放假走了,也就没有给我分配mt,然后有一个老哥在我来的时候给我发了一个landing手册,然后还有关于部门业务的白皮书,还有一些业务代码。然后本人是java面的,进来第一次接触go语言&nbsp;前面几天熟悉了一下go的语法和go的框架,可以读但是还不太会写,然后业务白皮书也看的很头疼,包括landing手册里要了解的很多东西说实话我看文档真的快看死了,一个嵌套一个,问题是我还完全不知道咋用这个我了解的东西,还有就是那个项目代码,那个老哥喊我去写写单测,熟悉一下go的语法,但也进行的很困难(这是我第一段实习,之前都是springboot那一套,真不太熟悉这个)想问问大家的建议,就是我从现在开始到在开年回来之前应该做些什么,我目前就一个想法&nbsp;就是复现一个landing手册上的go框架小项目&nbsp;就是相当于帮自己锻炼锻炼怎么写go&nbsp;或者各位大佬有没有更好的锻炼go语法的建议还有就是大家都在说vibe&nbsp;coding,那我应该怎么锻炼自己使用ai的能力,感觉我除了给一些需求然后它给我生成代码,好像就没别的用法了,那些什么工作流、拆解、skill啥的都不知道从哪一个地方开始,包括我现在正在实习,不知道精力该怎么分配,去网上想找找关于agent开发的一些学习流程,说实话,众说纷纭,有的是从python开始打基础然后系统学那些rag&nbsp;prompt&nbsp;langchain&nbsp;mcp等等,有的是说直接找一个github上的ai项目然后反复问ai,我确实有点迷茫,恳求各位大佬能留下你们宝贵的建议,我一定认真反复深刻学习有一说一&nbsp;我觉得字节饭挺好吃的!
双非后端失败第N人:1. go语言我建议你让ai带着你先把基本语法速通了,然后再去用go重新刷你以前刷过的leetcode,这样熟悉起来很快 2. 直接看你们组go项目,里面用***比较复杂,然后把每一个语法现象都喂给ai,一点点看
字节跳动公司福利 1371人发布
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务