【常见算法题】斐波那契数列(矩阵快速幂)

news2024/12/23 3:11:56

一、题目描述

大家都知道斐波那契数列,现在要求输入一个正整数 n ,请你输出斐波那契数列的第 n 项。

斐波那契数列满足如下

二、解题思路

2.1 普通处理方式

使用递归直接计算

int fib(int n) {
    if (n == 1 || n == 2) return 1;
    return fib(n - 1) + fib(n - 2);
}

这种方法是学习软件开发或者算法时最原始,最容易理解的方法。但是缺点很多

  • 1、未对n进行参数检查;
  • 2、性能差,数据量比较大的时候,计算很慢;
  • 3、返回结果时int类型,能支持的n很小;

2.2 分析优化及处理

如果使用递归求解,会重复计算一些子问题。例如,计算 f(4) 需要计算 f(3) 和 f(2),计算 f(3) 需要计算 f(2) 和 f(1),可以看到 f(2) 被重复计算了。

递归是将一个问题划分成多个子问题求解。动态规划也是如此,但是动态规划会把子问题的解缓存起来,从而避免重复求解子问题。

public int Fibonacci(int n) {
    if (n <= 1)
        return n;
    int[] fib = new int[n + 1];
    fib[1] = 1;
    for (int i = 2; i <= n; i++)
        fib[i] = fib[i - 1] + fib[i - 2];
    return fib[n];
}

考虑到第 i 项只与第 i-1 和第 i-2 项有关,因此只需要存储前两项的值就能求解第 i 项,从而将空间复杂度由 O(N) 降低为 O(1)。

尤其时n数值比较大时,可以降低很多内存占用,避免栈溢出。

public int Fibonacci(int n) {
    if (n <= 1)
        return n;
    int pre2 = 0, pre1 = 1;
    int fib = 0;
    for (int i = 2; i <= n; i++) {
        fib = pre2 + pre1;
        pre2 = pre1;
        pre1 = fib;
    }
    return fib;
}

三、再优化,支持更大的参数n和更大的返回结果

由于int只有4个字节,能处理的n比较小,因此可以考虑用long、BigInteger处理

3.1 long版本

对n进行参数检查,由于long是8个字节,要考虑到返回的结果不能超过long的最大值。

    public long fibLongPlus(int n) {
        if (n <= 0) {
            return 0;
        }
        if (n >= 92) {
            throw new IllegalArgumentException("n is too large to compute within available memory");
        }
        // base case
        long a = 0;
        long b = 1;
        // 状态转移
        long temp;
        for (int i = 2; i <= n; i++) {
            temp = a + b;
            a = b;
            b = temp;
        }
        return b;
    }

3.2 BigInteger版本

BigInteger可以支持更大的n,支持更大的结果返回

    public BigInteger fibPlus(int n) {
        if (n <= 0) {
            return BigInteger.ZERO;
        }
        // 考虑到内存和性能,n最大限制为1_000_000
        if (n > 1_000_000) {
            throw new IllegalArgumentException("n is too large to compute within available memory");
        }
        // base case
        BigInteger pre1 = BigInteger.ZERO;
        BigInteger pre2 = BigInteger.ONE;
        // 状态转移
        BigInteger fib;
        for (int i = 2; i <= n; i++) {
            fib = pre1.add(pre2);
            pre1 = pre2;
            pre2 = fib;
        }
        return pre2;
    }

四、再优化,使用`矩阵快速幂`提升计算性能

4.1 矩阵快速幂实现

    /**
     * 计算两个2x2矩阵的乘积。
     *
     * @param a 第一个矩阵
     * @param b 第二个矩阵
     * @return 两个矩阵的乘积
     */
    private BigInteger[][] matrixMultiply(BigInteger[][] a, BigInteger[][] b) {
        // 创建结果矩阵
        BigInteger[][] result = new BigInteger[2][2];   
        // 遍历结果矩阵的行
        for (int i = 0; i < 2; i++) {
            // 遍历结果矩阵的列  
            for (int j = 0; j < 2; j++) {
                // 初始化结果矩阵的当前元素为0
                result[i][j] = BigInteger.ZERO;
                // 遍历乘法的中间索引
                for (int k = 0; k < 2; k++) {
                    // 执行矩阵乘法
                    result[i][j] = result[i][j].add(a[i][k].multiply(b[k][j]));
                }
            }
        }
        // 返回结果矩阵
        return result;
    }

    /**
     * 使用矩阵快速幂算法计算矩阵的n次幂。
     *
     * @param matrix 要计算幂的矩阵
     * @param n      幂次
     * @return 矩阵的n次幂的结果
     */
    private BigInteger[][] matrixPower(BigInteger[][] matrix, int n) {
        BigInteger[][] result = new BigInteger[][]{
            // 初始化结果矩阵为单位矩阵 
            {BigInteger.ONE, BigInteger.ZERO},
            {BigInteger.ZERO, BigInteger.ONE}
        };

        // 当幂次大于0时循环
        while (n > 0) {
            // 如果幂次是奇数
            if ((n & 1) == 1) {
                // 将结果矩阵乘以原矩阵
                result = matrixMultiply(result, matrix);
            }

            // 将原矩阵平方
            matrix = matrixMultiply(matrix, matrix);

            // 幂次减半
            n >>= 1;
        }

        // 返回结果矩阵
        return result;
    }

    /**
     * 使用矩阵快速幂算法计算斐波那契数列的第n项。
     *
     * @param n 斐波那契数列的项数
     * @return 斐波那契数列的第n项
     */
    public BigInteger fibPlus(int n) {
        // 如果n小于等于0,返回0
        if (n <= 0) {
            return BigInteger.ZERO;
        }
        // 限制n大小
        if(n > 1_000_000_0){
            throw new IllegalArgumentException("n is too large to compute within available memory");
        }
        // 创建斐波那契变换矩阵
        BigInteger[][] fibMatrix = new BigInteger[][]{
            {BigInteger.ONE, BigInteger.ONE},
            {BigInteger.ONE, BigInteger.ZERO}
        };

        // 计算斐波那契变换矩阵的n-1次幂
        BigInteger[][] result = matrixPower(fibMatrix, n - 1);

        // 返回结果矩阵的第一行第一列元素,即斐波那契数列的第n项
        return result[0][0];
    }

4.2 单元测试

4.2.1 测试小数据量

    @Test
    public void test_fibPlus() {
        for (int i = 0; i < 100; i++) {
            // 设置要计算的斐波那契数列的项数
            int n = i;
            // 输出斐波那契数列的第n项
            System.out.println("Fibonacci(" + n + ") = " + fibPlus(n));
        }
    }

输出结果

Fibonacci(0) = 0
Fibonacci(1) = 1
Fibonacci(2) = 1
Fibonacci(3) = 2
Fibonacci(4) = 3
Fibonacci(5) = 5
Fibonacci(6) = 8
Fibonacci(7) = 13
Fibonacci(8) = 21
Fibonacci(9) = 34
Fibonacci(10) = 55
Fibonacci(11) = 89
Fibonacci(12) = 144
Fibonacci(13) = 233
Fibonacci(14) = 377
Fibonacci(15) = 610
Fibonacci(16) = 987
Fibonacci(17) = 1597
Fibonacci(18) = 2584
Fibonacci(19) = 4181
Fibonacci(20) = 6765
Fibonacci(21) = 10946
Fibonacci(22) = 17711
Fibonacci(23) = 28657
Fibonacci(24) = 46368
Fibonacci(25) = 75025
Fibonacci(26) = 121393
Fibonacci(27) = 196418
Fibonacci(28) = 317811
Fibonacci(29) = 514229
Fibonacci(30) = 832040
Fibonacci(31) = 1346269
Fibonacci(32) = 2178309
Fibonacci(33) = 3524578
Fibonacci(34) = 5702887
Fibonacci(35) = 9227465
Fibonacci(36) = 14930352
Fibonacci(37) = 24157817
Fibonacci(38) = 39088169
Fibonacci(39) = 63245986
Fibonacci(40) = 102334155
Fibonacci(41) = 165580141
Fibonacci(42) = 267914296
Fibonacci(43) = 433494437
Fibonacci(44) = 701408733
Fibonacci(45) = 1134903170
Fibonacci(46) = 1836311903
Fibonacci(47) = 2971215073
Fibonacci(48) = 4807526976
Fibonacci(49) = 7778742049
Fibonacci(50) = 12586269025
Fibonacci(51) = 20365011074
Fibonacci(52) = 32951280099
Fibonacci(53) = 53316291173
Fibonacci(54) = 86267571272
Fibonacci(55) = 139583862445
Fibonacci(56) = 225851433717
Fibonacci(57) = 365435296162
Fibonacci(58) = 591286729879
Fibonacci(59) = 956722026041
Fibonacci(60) = 1548008755920
Fibonacci(61) = 2504730781961
Fibonacci(62) = 4052739537881
Fibonacci(63) = 6557470319842
Fibonacci(64) = 10610209857723
Fibonacci(65) = 17167680177565
Fibonacci(66) = 27777890035288
Fibonacci(67) = 44945570212853
Fibonacci(68) = 72723460248141
Fibonacci(69) = 117669030460994
Fibonacci(70) = 190392490709135
Fibonacci(71) = 308061521170129
Fibonacci(72) = 498454011879264
Fibonacci(73) = 806515533049393
Fibonacci(74) = 1304969544928657
Fibonacci(75) = 2111485077978050
Fibonacci(76) = 3416454622906707
Fibonacci(77) = 5527939700884757
Fibonacci(78) = 8944394323791464
Fibonacci(79) = 14472334024676221
Fibonacci(80) = 23416728348467685
Fibonacci(81) = 37889062373143906
Fibonacci(82) = 61305790721611591
Fibonacci(83) = 99194853094755497
Fibonacci(84) = 160500643816367088
Fibonacci(85) = 259695496911122585
Fibonacci(86) = 420196140727489673
Fibonacci(87) = 679891637638612258
Fibonacci(88) = 1100087778366101931
Fibonacci(89) = 1779979416004714189
Fibonacci(90) = 2880067194370816120
Fibonacci(91) = 4660046610375530309
Fibonacci(92) = 7540113804746346429
Fibonacci(93) = 12200160415121876738
Fibonacci(94) = 19740274219868223167
Fibonacci(95) = 31940434634990099905
Fibonacci(96) = 51680708854858323072
Fibonacci(97) = 83621143489848422977
Fibonacci(98) = 135301852344706746049
Fibonacci(99) = 218922995834555169026

4.2.2 测试大数据量

n=1_000_000 时很快,不到1秒。

再大一些(比如n=1_000_000_0)的时候,计算就比较慢了

  • 测试 1_000_000_0
    @Test
    public void test_fibPlus() {
        int n = 1_000_000_0;
        System.out.println("Fibonacci(" + n + ") = " + fibPlus(n));
    }

测试大数结果(结果太长了,发布不了,就不展示了,感兴趣的可以自己写UT验证下)

Fibonacci(10000000) = 1129834378225399760317063637745866372944837190489040881513577643245534731167933137524219777458247745488503329541529737982917618975273928543637913029320511080393607160947067632276156828424897006419736620682555596286851200164878524757142799029763435331462543748832574728019186803442609337613122078718093224952473835489645047696411558824438103526892104885863028289108.............

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2044207.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

实现信创Linux麦克风摄像头录制(源码,银河麒麟、统信UOS)

随着信创国产化浪潮的来临&#xff0c;在国产操作系统上的应用开发的需求越来越多&#xff0c;其中一个就是需要在银河麒麟或统信UOS上实现录制摄像头视频和麦克风声音&#xff0c;将它们录制成一个mp4文件。那么这个要如何实现了&#xff1f; 一. 技术方案 要完成这些功能&a…

北大研究生公选课资料现已公开,数据库学习秘籍速来get!

为促进基础软件在中国高校的传播&#xff0c;进一步提高在校研究生对基础软件的学习和开发实践能力&#xff0c;拓数派与开源联盟 PG 分会携手合作&#xff0c;走进北京大学&#xff0c;进行了北大软件与微电子学院 2024 年《北京大学 PostgreSQL 内核开发&#xff1a;从入门到…

构建高效沃尔玛自养号测评系统:技术策略与实战指南

搭建沃尔玛自养号测评技术系统是一个涉及多方面技术和资源投入的过程&#xff0c;旨在通过自行构建和掌控测评环境&#xff0c;利用真实国外买家的信息和资料来创建买家账号&#xff0c;模拟真实的购买和评价过程&#xff0c;从而提升商品权重和销量。以下是搭建该系统的主要步…

mysql Ubuntu安装与远程连接配置

一、安装&#xff08;Ubuntu22环境安装mysql8&#xff09; 这里使用Xshell链接Ubuntu和mysql windows进行操作&#xff0c;特别提醒&#xff1a;安装之前建议对Ubuntu快照处理备份&#xff0c;避免安装中出错导致Ubuntu崩溃。 查看是否安装的有可以用指令&#xff1a;ps -ef|…

IOS 05 OC和Swift混合编程

为什么需要使用OC和Swift混合编程&#xff1f; 在真实项目开发过程中&#xff0c;大部分时候我们往往都会使用到OC和Swift混合编程&#xff0c;主要原因如下&#xff1a; 老项目是OC语言实现的&#xff0c;但需要引用Swift的框架&#xff1b;新项目是Swift实现的&#xff0c;…

【操作系统】二、进程管理:1.进程与线程(程序、进程(PCB、状态转换、原语、进程间通信)、线程(多线程模型))

二、进程与线程 文章目录 二、进程与线程1.程序1.1顺序执行的特征1.2并发执行的特征 2.进程Process2.1定义&#xff08;组织&#xff09;2.1.1程序段2.1.2数据段2.1.3进程控制块PCB1&#xff09;内容2&#xff09;作用3&#xff09;进程组织方式 2.2特征2.3进程的状态与转换2.3…

云服务器是什么?云服务器可以用来干什么?

云服务器&#xff0c;顾名思义&#xff0c;是指运行在云计算环境中的虚拟服务器。与传统的物理服务器相比&#xff0c;云服务器不需要用户自行购买、搭建和维护硬件设备&#xff0c;而是通过互联网从云服务提供商处获取计算资源、存储空间和网络服务。用户可以根据自己的需求&a…

spring揭秘05-ApplicationContext

文章目录 【README】【1】ApplicationContext概述【1.1】spring通过Resource对文件抽象【1.2】统一资源加载策略-ResourceLoader【1.2.1】 DefaultResourceLoader【1.2.2】FileSystemResourceLoader【1.2.3】 ResourcePatternResolver批量加载资源【1.2.4】Resource与ResourceL…

使用住宅代理抓取奥运奖牌新闻,全面掌握赛事精彩瞬间

引言 什么是新闻抓取&#xff1f;目的是什么&#xff1f; 新闻抓取有哪些好处&#xff1f; 为什么需要关注奥运奖牌新闻&#xff1f; 如何进行新闻抓取——以Google 新闻为例 总结 引言 近日&#xff0c;巴黎奥运会圆满落幕&#xff0c;在这16天中&#xff0c;全球顶尖运…

一问讲透什么是 RAG,为什么需要 RAG?

一. 为什么要用 RAG &#xff1f; 如果使用 pretrain 好的 LLM 模型&#xff0c;应用在你个人的情境中&#xff0c;势必会有些词不达意的地方&#xff0c;例如问 LLM 你个人的信息&#xff0c;那么它会无法回答;这种情况在企业内部也是一样&#xff0c;例如使用 LLM 来回答企业…

VTK—vtkRectilinearGrid学习

vtkRectilinearGrid理解为沿着坐标轴方向一系列规格的网格&#xff0c;但是网格间距可以不同。需要显式的提供各坐标轴的点数据。单元数据不用指定&#xff0c;会隐式生成。与前面提到的vtkStructuredGrid 类似&#xff0c;但是每个网格线都是直的。 1.给三个坐标分配点&#…

Golang基于DTM的分布式事务SAGA实战

SAGA介绍 SAGA是“长时间事务”运作效率的方法&#xff0c;大致思路是把一个大事务分解为可以交错运行的一系列子事务的集合。原本提出 SAGA 的目的&#xff0c;是为了避免大事务长时间锁定数据库的资源&#xff0c;后来才逐渐发展成将一个分布式环境中的大事务&#xff0c;分…

关于tresos Studio(EB)的MCAL配置之DIO

General Dio Development Error Detect开发者错误检测 Dio Flip Channel Api翻转通道电平接口Dio_FlipChannel是否启用 Dio Version Info Api决定Dio_GetVersionInfo接口是否启用&#xff0c;一般打开就行。 Dio Reverse Port Bits让端口的位&#xff08;通道&#xff09;进…

最新号卡推广单页源码/仿制手机卡流量卡号卡代理推广源码/简洁实用/带弹窗公告+后台管理

源码简介&#xff1a; 最新号卡推广单页源码&#xff0c;它是手机卡流量卡号卡代理推广源码量身打造的&#xff0c;不仅设计得简洁实用&#xff0c;而且还有炫酷的弹窗公告功能和强大的后台管理系统哦&#xff01; 一款号卡推广单页源码&#xff0c;自己仿制来的&#xff0c;…

arcgis-坡度坡向分析

坡向的描述有定性和定量两种方式&#xff0c;定量是以东为0&#xff0c;顺时针递增&#xff0c;南为90&#xff0c;西为180&#xff0c;北为270等&#xff0c;范围在0&#xff5e;35959′59″之间。 定性描述有8方向法和4方向法. 8 方向为东、东南、南、西南、西、西北、北、东…

Linux安装jdk8,tomcat和mysql

目录 Linux安装jdk8 第一步&#xff1a;下载jdk8 第二步&#xff1a;把下载好的压缩包通过finalShell发送到linux虚拟机上 ​编辑 第三步&#xff1a;解压缩 第四步&#xff1a;配置环境变量 第五步&#xff1a;重新加载profile配置文件 第六步&#xff1a;检查是否安装成…

C++ -- 负载均衡式在线OJ (三)

文章目录 四、oj_server模块1. oj_server的功能路由2. 建立文件版的题库3. model模块4.controller模块5.judge模块&#xff08;负载均衡&#xff09;6.view模块整体代码结构&#xff08;前端的东西&#xff0c;不是重点&#xff09; 五、最终效果项目源码 前面部分请看这里C –…

Unite Shanghai 2024 团结引擎专场 | 团结引擎实时全局光照

在 2024 年 7 月 24 日的 Unite Shanghai 2024 团结引擎专场演讲中&#xff0c;Unity 中国高级技术经理周赫带大家深入解析了团结引擎的实时全局光照系统。该系统支持完全动态的场景、动态材质和动态灯光的 GI 渲染&#xff0c;包括无限次弹射的漫反射和镜面反射 GI。 周赫&…

2024年职场常备!3款高效数据恢复软件免费版,让打工人工作无忧

嘿&#xff0c;职场的朋友们&#xff01;咱们现在工作&#xff0c;数据就跟空气一样重要&#xff0c;对吧&#xff1f;但有时候&#xff0c;手一滑&#xff0c;文件没了&#xff0c;硬盘突然就挂了&#xff0c;系统也闹点小情绪&#xff0c;那心情&#xff0c;比股市大跌还难受…

基于Django的boss直聘数据分析可视化系统的设计与实现

研究背景 随着互联网的发展&#xff0c;在线招聘平台逐渐成为求职者与企业之间的重要桥梁。Boss直聘作为国内领先的招聘平台&#xff0c;以其独特的直聊模式和高效的匹配算法&#xff0c;吸引了大量的用户。然而&#xff0c;随着平台用户的增长&#xff0c;海量的招聘数据带来…