【算法】动态规划-斐波那契数列模型

news2025/1/17 3:53:25

目录

1、第N个泰波那契数

1.1 算法原理讲解

1.1.1 状态表示

1.1.2 状态转移方程

1.1.3 初始化

1.1.4 填表顺序

1.1.5 返回值

1.2 代码实现

1.3 空间优化

2、三步问题

2.1 算法原理讲解

2.1.1 状态表示

2.1.2 状态转移方程

2.1.3 初始化

2.1.4 填表顺序

2.1.5 返回值

2.2 代码实现

3、使用最小花费爬楼梯

3.1 解法一

3.1.1 状态表示

3.1.2 状态转移方程

3.1.3 初始化

3.1.4 填表顺序

3.1.5 返回值

3.1.6 代码实现

3.2 解法二

3.2.1 状态表示

3.2.2 状态转移方程

3.2.3 初始化

3.2.4 填表顺序

3.2.5 返回值

3.2.6 代码实现

4、解码方法


1、第N个泰波那契数

1.1 算法原理讲解

动态规划就是创建1个一维数组或者二维数组作为DP表,填DP表,DP表中的某一项就是结果

动态规划的步骤通常分为5步

1.1.1 状态表示

是什么? dp表中每一项所代表的含义

怎么来? 1. 题目要求

               2. 经验 + 题目要求

               3. 分析问题的过程中,发现重复子问题 

这道题是需要返回第n个泰斐波那契数列的值,所以dp[i]就表示第i个泰斐波那契数列的值

1.1.2 状态转移方程

动态规划的最终结果就是要将dp表填满,所以应该要知道dp[i]等于什么,即用之前的状态或之后的状态来表示当前的状态

状态转移方程就是dp[i]等于什么

这道题中dp[i] = dp[i - 1] + dp[i - 2] + dp[i - 3]

1.1.3 初始化

初始化是为了在填表的时候不会越界

这道题n是从0开始的,那么dp[0],dp[1],dp[2],放入状态转移方程中都会造成数值越界,所以需要对这几个值进行初始化

由题意可知,dp[0] = 0,dp[1] = dp[2] = 1

1.1.4 填表顺序

确定是从左向右开始填,还是从右向左开始填

依据就是,为了填写当前状态,所需要的状态已经计算过了

像这道题,填写当前状态,需要它的前三个状态已经填写过,所以是从左向右填

1.1.5 返回值

题目要求 + 状态表示

这道题就是dp[n]

1.2 代码实现

class Solution {
public:
    int tribonacci(int n) {
        if(n == 0) return 0;
        if(n == 1 || n == 2) return 1;
        vector<int> dp(n + 1);
        dp[0] = 0,dp[1] = dp[2] = 1;
        for(int i = 3;i<=n;i++)
        {
            dp[i] = dp[i - 1] + dp[i - 2] + dp[i - 3];
        }
        return dp[n];
    }
};

1.3 空间优化

这道题中,当依次向后求dp[i]时,前面的有些状态是可以舍弃的,所以可以利用滚动数组优化

优化可以将空间复杂度O(N^2) -> O(N) , O(N) -> O(1)

class Solution {
public:
    int tribonacci(int n) {
        if(n == 0) return 0;
        if(n == 1 || n == 2) return 1;
        int a = 0,b = 1,c = 1,d = 0;
        for(int i = 3;i<=n;i++)
        {
            d = a + b + c;
            a = b;b = c;c = d;
        }
        return d;
    }
};

2、三步问题

2.1 算法原理讲解

2.1.1 状态表示

dp[i]表示到达i位置一共有多少种方法

2.1.2 状态转移方程

第n个台阶可以从第n-1个台阶跨1步上去,也可以从第n-2个台阶跨2步上去,还可以从第n-3个台阶跨3步上去,所以dp[i] = dp[i - 1] + dp[i - 2] + dp[i - 3]

2.1.3 初始化

这道题n是从1开始的,那么dp[1],dp[2],dp[3],放入状态转移方程中都会造成数值越界,所以需要对这几个值进行初始化

2.1.4 填表顺序

这道题,填写当前状态,需要它的前三个状态已经填写过,所以是从左向右填

2.1.5 返回值

返回dp[n]

2.2 代码实现

class Solution {
public:
    int waysToStep(int n) {
        const int MOD = 1000000007;// 即le9 + 7

        //处理边界条件
        if(n == 1 || n == 2) return n;
        if(n == 3) return 4;
        vector<int> dp(n + 1);
        dp[1] = 1,dp[2] = 2,dp[3] = 4;
        for(int i = 4;i<=n;i++)
            dp[i] = ((dp[i - 1] + dp[i - 2]) % MOD + dp[i - 3]) % MOD;
            //注意这里每加一个就要取模,否则可能前两个相加就超过了int的最大范围
        return dp[n];
    }
};

注意:题目中说结果可能很大,所以每加1次就要取一次模

3、使用最小花费爬楼梯

3.1 解法一

3.1.1 状态表示

dp[i]表示到达第i阶台阶所需要的最小花费

3.1.2 状态转移方程

第i阶台阶是从第i - 1阶或第i - 2阶台阶上来的,所以到达第i阶台阶所需要的最小花费就等于到达i-1阶台阶的最小花费加上i-1阶台阶的花费、i-2阶台阶的最小花费加上i - 2阶台阶的花费,这二者中的较小值

dp[i] = min(dp[i - 1] + cost[i - 1] , dp[i - 2] + cost[i - 2])

3.1.3 初始化

需要对dp[0]和dp[1]初始化,因为可以选择从下标为 0 或 1 的元素作为初始阶梯

所以,dp[0] = dp[1] = 0

3.1.4 填表顺序

从左向右

3.1.5 返回值

dp[n]

3.1.6 代码实现
class Solution {
public:
    int minCostClimbingStairs(vector<int>& cost) {
        // 解法一:dp表的第i项表示到第i阶台阶所需要的体力值
        int n = cost.size();
        vector<int> dp(n + 1);// dp表的第i项表示到第i阶台阶所需要的体力值
        dp[0] = dp[1] = 0;// 因为可以选择从第1阶或第2阶开始走
        for(int i = 2;i<=n;i++)
        {
            dp[i] = min(dp[i - 1] + cost[i - 1],dp[i - 2] + cost[i - 2]);
        }
        return dp[n];
    }
};

3.2 解法二

3.2.1 状态表示

dp[i]表示从第i项到顶部所需要的最小花费

3.2.2 状态转移方程

从第i阶台阶可以向上走1步或者2步,所以从第i阶台阶到顶部的最小花费就等于这一步台阶的花费,加上从第i + 1阶台阶到顶部的最小花费或从第i + 2阶台阶到顶部的最小花费中的较小值

dp[i] = min(dp[i - 1] , dp[i - 2]) + cost[i]

3.2.3 初始化

需要对dp[n - 1]和dp[n - 2]初始化

dp[n - 1] = cost[n - 1],dp[n - 2] = cost[n - 2]

3.2.4 填表顺序

从右向左

3.2.5 返回值

返回dp[0]或dp[1]中的较小值,因为可以选择从下标为 0 或 1 的元素作为初始阶梯

3.2.6 代码实现
class Solution {
public:
    int minCostClimbingStairs(vector<int>& cost) {
        // 解法二:dp表的第i项表示从第i阶台阶到顶层所需要的体力值
        int n = cost.size();
        vector<int> dp(n);
        dp[n - 1] = cost[n - 1],dp[n - 2] = cost[n - 2];
        for(int i = n - 3;i>=0;i--)
        {
            dp[i] = min(dp[i + 1],dp[i + 2]) + cost[i];
        }
        return min(dp[0],dp[1]);
    }
};

4、解码方法

dp[i]表示当前位置有几种解码方法

根据题目的意思,0不能单独组成一个编码,也不能在与其他字符组成编码时位于首位

此时需要分两种情况讨论:

1. s[i]可以自身解码,即s[i] != '0'

若s[i]还能和s[i - 1]共同解码,即s[i]和s[i - 1]组成的数字范围在[10,26],dp[i] = dp[i - 1] + dp[i - 2]

若s[i]不能和s[i - 1]共同解码,dp[i] = dp[i - 1]

2. s[i]不能自身解码,即s[i] == '0'

若s[i]还能和s[i - 1]共同解码,即s[i]和s[i - 1]组成的数字范围在[10,26],dp[i] = dp[i - 2]

若s[i]不能和s[i - 1]共同解码,dp[i] = 0

通过上面可知,为了防止溢出,我们需要对dp[0]和dp[1]初始化

1. 当s[0] == '0',dp[0] = dp[1] = 0

2. 当s[0] != '0',dp[0] = 1,若dp[1]无法自身解码,也无法和dp[0]共同解码,则dp[1] = 0,若dp[1]可以自身解码,也可以和dp[0]共同解码,则dp[1] = 2,若dp[1]可以自身解码和dp[1]可以于dp[0]共同解码只有一个为真,则dp[1] = 1

返回值就是dp[n - 1]

class Solution {
public:
    int numDecodings(string s) {
        int n = s.size();
        if(n == 1) return s[0] == '0'? 0 : 1;
        vector<int> dp(n);//dp是记录当前位置一共有几种解码方法
        if(s[0] == '0')// s[0]无法自身解码
        {
            dp[0] = dp[1] = 0;// 当s[0] == '0',则下标1处的解码方法数为0,因为0后面加一个数是解码失败的
        }
        else// s[0]可以自身解码
        {
            dp[0] = 1;
            int x = (s[0] - '0') * 10 + s[1] - '0';// 记录s[0] 和 s[1]组成的数字是多少
            if(s[1] == '0' && x >27) dp[1] = 0;// s[1]无法自身解码,也无法与s[0]共同解码
            else if(s[1] != '0' && x >= 10 && x <= 26) dp[1] = 2;//s[1]可以自身解码,也可与s[0]共同解码
            else dp[1] = 1;// s[1]只能自身解码或者与s[0]共同解码
        }
        for(int i = 2;i<n;i++)
        {
            if(s[i] == '0')// 自身无法解码
            {
                int x = (s[i - 1] - '0') * 10 + s[i] - '0';
                if(x >= 10 && x <= 26) dp[i] = dp[i - 2];// 可以与前一个共同解码
                else dp[i] = 0;// 无法与前一个共同解码
            }
            else// 自身可以解码
            {
                int x = (s[i - 1] - '0') * 10 + s[i] - '0';
                if(x >= 10 && x <= 26) dp[i] = dp[i -1] + dp[i - 2];// 可以与前一个共同解码
                else dp[i] = dp[i - 1];// 无法与前一个共同解码
            }
        }
        return dp[n - 1];
    }
};

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

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

相关文章

(四十一)大数据实战——spark的yarn模式生产环境部署

前言 Spark 是一个开源的分布式计算系统。它提供了高效的数据处理能力&#xff0c;支持复杂的数据分析和处理任务&#xff0c;是一种基于内存的快速、通用、可扩展的大数据分析计算引擎。Spark Core&#xff1a;实现了Spark的基本功能&#xff0c;包含任务调度、内存管理、错误…

上线前端系统

上线一个静态的前端系统&#xff08;续&#xff09; 在eleme服务器上 启动服务 启动rpcbind [rooteleme-static ~]# systemctl restart rpcbind 启动nfs [rooteleme-static ~]# systemctl restart nfs 重启服务 启动smb [rootstatic-server img]# systemctl start smb…

SQL数据库模糊查询指定的字符的表资料(CHARINDEX)

1.目的 MSG栏位里面有很多组合内容的字符信息&#xff0c;需要进行模糊查询。 2.问题 正常使用LIKE 语句可以通用大部分的查询需求&#xff0c;但是遇到部分的特殊字符&#xff0c;例如&#xff1a;[] 资料是存在数据资料中&#xff0c;但是查询反馈的结果是没有内容&#xf…

二刷代码随想录训练营Day 16|513.找树左下角的值、112.路径总和、106.从中序与后序遍历序列构造二叉树

1.找到左下角的值 513. 找树左下角的值 - 力扣&#xff08;LeetCode&#xff09;代码随想录 (programmercarl.com) 代码&#xff1a; class Solution { public:int maxDepth INT_MIN;int result;// 深度最大&#xff0c;确保是最后一行 先遍历左孩子再遍历右孩子 确保是左下…

进程地址空间,零基础最最最详解

目录 建议全文阅读&#xff01;&#xff01;&#xff01; 建议全文阅读&#xff01;&#xff01;&#xff01; 建议全文阅读&#xff01;&#xff01;&#xff01; 一、什么是地址空间 1、概念 2、主要组成部分 3、特点和作用 &#xff08;1&#xff09;虚拟化&#xf…

Java并发—volatile关键字

在这篇文章Java并发—Java内存模型以及线程安全-CSDN博客多次提及volatile关键字&#xff0c;这是一个非常重要的概念&#xff0c;主要用于多线程编程中&#xff0c;它确保了变量的可见性和禁止指令重排序&#xff0c;但不保证原子性&#xff0c;下面详细解释volatile关键字的作…

未来3-5年,哪些工作会被AI取代

一篇由高盛经济学家约瑟夫布里格斯 &#xff08;Joseph Briggs&#xff09;和德维西科德纳尼 &#xff08;Devesh Kodnani&#xff09;撰写的报告指出&#xff0c;全球预计将有3亿个工作岗位被生成式AI取代。 报告称&#xff1a;“最近出现的生成式人工智能将降低劳动力成本和…

​宁德时代:续航还剩多少?

车企价格战打到供应商&#xff0c;连续增利不增收。 今天我们看宁德时代的增长电池续航还剩多少&#xff1f; 巨头长成&#xff0c;就要面临增长瓶颈。“宁王”24年中报公布&#xff0c;业绩喜忧参半。二季度营收869.96亿&#xff0c;同比下滑13.18%&#xff0c; 已经是宁德时…

冠军之选:奥运冠军青睐的游泳耳机款式大公开

在最新一届的夏季奥林匹克运动会中&#xff0c;泳池边的激烈竞争再次点燃了全球观众的热情。游泳运动员们&#xff0c;以惊人的速度和毅力&#xff0c;一次又一次地刷新纪录&#xff0c;向世人展示了人类极限的无限可能。而在这些运动员备战的过程中&#xff0c;有一个细节或许…

吴恩达老师机器学习-ex5

有借鉴网上部分博客 首先&#xff0c;我先使用该数据集&#xff0c;通过线性回归的方法&#xff0c;做了一个预测问题 import numpy as np import scipy.io as sio import matplotlib.pyplot as plt from scipy.optimize import minimize#读取数据 path "./ex5data1.ma…

Spine 核心功能入门

核心功能入门 本文主旨是整理我在入手学习 spine 时的流程&#xff0c;以及对于基本功能的理解和常规 2D 动画实现的思路。 意在整理出一个简要的入门 spine 的流程&#xff0c;以及对于一些高阶功能的应用的思考。 本文基于 https://zh.esotericsoftware.com/ 官网教程进行思…

2024.8.1 作业

使用两个线程完成两个文件的拷贝&#xff0c;分支线程1拷贝前一半&#xff0c;分支线程2拷贝后一半&#xff0c;主线程回收两个分支线程的资源 #include <myhead.h>struct Buf {const char *file1;const char *file2;int start;int size; };int get_len(const char *arr…

从线段中搜寻提取闭合轮廓(二)

接上篇文章从线段中搜寻闭合轮廓_多线段搜索区域集合 快速-CSDN博客 1. 前言 调试了上篇文章中参考代码修了一些问题&#xff0c;优化了显示&#xff0c;但是由于算法逻辑存在一些问题&#xff0c;有很多不必要的性能损耗&#xff0c;且逻辑不是最优的&#xff0c;于是博主找…

FPGA开发——蜂鸣器实现音乐播放器的设计

一、概述 我们在进行蜂鸣器的学习的时候&#xff0c;总会在想既然蜂鸣器能够发出声音&#xff0c;那么它能够播放音乐吗&#xff0c;今天这篇我们文章我们就一起来学习怎样使用使用蜂鸣器来播放音乐&#xff0c;也就是怎样成为一个音乐播放器。 1、蜂鸣器的类型 在设计的时候…

玩机进阶教程-----手机恢复出厂 误删除照片视频 误刷机后 几种数据恢复操作步骤解析【一】

手机中存储有众多的照片 视频 文件或者电话本这类的数据,虽然目前很多机型都有云存储。可以随时同步手机的存储数据。但万一云存储没有开启同步或者密码忘记。或者恢复出厂等等原因造成以上的数据丢失。或者手机系统问题导致的不开机但需要其中的数据等等。那么如何简单快速的…

【项目日记(五)】梦幻笔耕-测试报告

❣博主主页: 33的博客❣ ▶️文章专栏分类:项目日记◀️ &#x1f69a;我的代码仓库: 33的代码仓库&#x1f69a; &#x1faf5;&#x1faf5;&#x1faf5;关注我带你了解更多项目内容 目录 1.项目背景2.测试环境3.测试计划3.1功能测试3.2自动化测试 1.项目背景 个人博客系统…

剪画小程序:自媒体创业者的准备-文案!

作为一个刚刚踏入自媒体领域的新人&#xff0c;我满怀着激情和憧憬&#xff0c;渴望通过视频分享自己的见解和生活点滴。然而&#xff0c;视频文案的创作却成了我面前难以逾越的高山。 在构思旅行视频时&#xff0c;面对美丽的风景和丰富的经历&#xff0c;我却无法用恰当的文字…

嵌入式学习Day19---Linux软件编程

目录 一、标准I/O 1.1.fseek 1.偏移量 2.实例 ​编辑 1.2.ftell 2.实例 ​编辑 二、文件I/O 2.1.打开文件 1.open 2.2.实例 2.2.读写文件 1.write 实例 ​编辑 2.read 实例 2.3.关闭文件 1.close 2.3.lseek 实例 三、标准I/O与文件I/O的区别 3.1.区别 四、其…

2024年有哪些开放式耳机值得入手?精选五大高分品牌

近几年兴起的开放式蓝牙耳机&#xff0c;具有佩戴舒适稳固、不影响使用者判断外界环境等优点&#xff0c;十分适合在户外环境下使用&#xff0c;因此受到了众多健身人士的喜爱。那么该如何挑选到一款适合自己的开放式耳机呢&#xff1f;2024年有哪些开放式耳机值得入手&#xf…

【架构】应用保护

这篇文章总结一下应用保护的手段。如今说到应用保护&#xff0c;更多的会想到阿里的sentinel&#xff0c;手段丰富&#xff0c;应用简单。sentinel的限流、降级、熔断&#xff0c;可以自己去试一下&#xff0c;sentinel主要通过配置实现功能&#xff0c;不难。sentinel的简介放…