算法沉淀——动态规划之斐波那契数列模型(leetcode真题剖析)

本文主要是介绍算法沉淀——动态规划之斐波那契数列模型(leetcode真题剖析),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

在这里插入图片描述

算法沉淀——动态规划之斐波那契数列模型

  • 01.第 N 个泰波那契数
  • 02.三步问题
  • 03.使用最小花费爬楼梯
  • 04.解码方法

动态规划(Dynamic Programming,简称DP)是一种通过将原问题分解为相互重叠的子问题并仅仅解决每个子问题一次,将其解存储起来,避免重复计算,从而提高效率的算法优化技术。它通常用于求解最优化问题。

动态规划的基本思想是利用之前已经计算过的结果,通过递推关系式来计算当前问题的解。它具有以下关键特点:

  1. 最优子结构: 问题的最优解可以通过其子问题的最优解来构造。
  2. 重叠子问题: 原问题可以分解为若干个子问题,而这些子问题可能会被多次重复求解。
  3. 状态转移方程: 动态规划通过定义状态和状态之间的转移关系来描述问题。通过状态转移方程,我们可以推导出问题的最优解。

动态规划可以分为两种主要类型:自顶向下的记忆化搜索和自底向上的递推法。

  • 自顶向下(Top-Down): 利用递归的方式,通过递归调用来解决问题,并通过记忆化技术来避免重复计算,即缓存已经计算过的子问题的结果。
  • 自底向上(Bottom-Up): 从最小的子问题开始,通过迭代的方式逐步求解规模更大的问题,通常使用数组或表格来存储子问题的结果,避免了递归的开销。

动态规划常用于求解具有最优子结构和重叠子问题性质的问题,例如最短路径问题、背包问题、编辑距离问题等。

典型的动态规划问题包括:

  • 斐波那契数列: F(n) = F(n-1) + F(n-2)。
  • 最长上升子序列(LIS): 求一个序列中最长的递增子序列的长度。
  • 背包问题: 在给定限制条件下,选择最优的物品放入背包,使得价值最大。
  • 最短路径问题: 求图中两个节点之间的最短路径。
  • 编辑距离问题: 计算两个字符串之间的最小编辑操作次数。

动态规划算法是一种强大的算法设计思想,可以在很多问题中得到应用,但需要注意问题是否满足最优子结构和重叠子问题的条件。

01.第 N 个泰波那契数

题目链接:https://leetcode.cn/problems/n-th-tribonacci-number/

泰波那契序列 Tn 定义如下:

T0 = 0, T1 = 1, T2 = 1, 且在 n >= 0 的条件下 Tn+3 = Tn + Tn+1 + Tn+2

给你整数 n,请返回第 n 个泰波那契数 Tn 的值。

示例 1:

输入:n = 4
输出:4
解释:
T_3 = 0 + 1 + 1 = 2
T_4 = 1 + 1 + 2 = 4

示例 2:

输入:n = 25
输出:1389537

提示:

  • 0 <= n <= 37
  • 答案保证是一个 32 位整数,即 answer <= 2^31 - 1

思路

动态规划的题目主要分为5个步骤。

1.状态表示:这道题可以根据题目要求直接给出,dp[i]等于第i个泰波纳契数。

2.状态转移方程:同样原题已经给出,dp[i] = dp[i - 1] + dp[i - 2] + dp[i - 3] 。

3.初始化:我们只需要将前三个数初始化即可,防止越界。

4.填表顺序:从左往右。

5.返回值:dp[n]。

代码

class Solution {
public:int tribonacci(int n) {int dp[38];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];}
};

02.三步问题

题目链接:https://leetcode.cn/problems/three-steps-problem-lcci/

三步问题。有个小孩正在上楼梯,楼梯有n阶台阶,小孩一次可以上1阶、2阶或3阶。实现一种方法,计算小孩有多少种上楼梯的方式。结果可能很大,你需要对结果模1000000007。

示例1:

 输入:n = 3 输出:4说明: 有四种走法

示例2:

 输入:n = 5输出:13

提示:

  1. n范围在[1, 1000000]之间

思路

  1. 状态表示: 定义状态 dp[i] 表示到达第 i 个位置时的总方法数。
  2. 状态转移方程: 根据题目要求,dp[i] 可以由前三个状态转移而来:dp[i - 1]dp[i - 2]dp[i - 3]。因此,状态转移方程为 dp[i] = dp[i - 1] + dp[i - 2] + dp[i - 3]
  3. 取模操作: 由于题目要求结果取模,需要在每次计算时都进行取模操作,防止整数溢出。
  4. 初始化: 需要初始化前三个位置的值,即 dp[1] = 1dp[2] = 2dp[3] = 4
  5. 填表顺序: 从左到右填表,逐步计算出 dp[i] 的值。
  6. 返回值: 返回 dp[n] 的值,即到达第 n 个位置时的总方法数。

这样的动态规划问题通常可以通过填表的方式解决,遵循以上步骤可以得到最终的解。

代码

class Solution {const int Mod = 1e9+7;
public:int waysToStep(int n) {if(n==1||n==2) return n;if(n==3) return 4;vector<long long> 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;return dp[n];}
};

03.使用最小花费爬楼梯

题目链接:https://leetcode.cn/problems/min-cost-climbing-stairs/

给你一个整数数组 cost ,其中 cost[i] 是从楼梯第 i 个台阶向上爬需要支付的费用。一旦你支付此费用,即可选择向上爬一个或者两个台阶。

你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。

请你计算并返回达到楼梯顶部的最低花费。

示例 1:

输入:cost = [10,15,20]
输出:15
解释:你将从下标为 1 的台阶开始。
- 支付 15 ,向上爬两个台阶,到达楼梯顶部。
总花费为 15 。

示例 2:

输入:cost = [1,100,1,1,1,100,1,1,100,1]
输出:6
解释:你将从下标为 0 的台阶开始。
- 支付 1 ,向上爬两个台阶,到达下标为 2 的台阶。
- 支付 1 ,向上爬两个台阶,到达下标为 4 的台阶。
- 支付 1 ,向上爬两个台阶,到达下标为 6 的台阶。
- 支付 1 ,向上爬一个台阶,到达下标为 7 的台阶。
- 支付 1 ,向上爬两个台阶,到达下标为 9 的台阶。
- 支付 1 ,向上爬一个台阶,到达楼梯顶部。
总花费为 6 。

提示:

  • 2 <= cost.length <= 1000
  • 0 <= cost[i] <= 999

思路一

  1. 状态表示: 定义状态 dp[i] 表示到达第 i 个位置时的最小花费。注意:到达第 i 个位置的时候,第 i 位置的花费不需要算上。
  2. 状态转移方程: 根据题目要求,dp[i] 可以由前两个状态转移而来:dp[i - 1]dp[i - 2]。因此,状态转移方程为 dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2])
  3. 初始化: 需要初始化前两个位置的值,即 dp[0] = dp[1] = 0,因为不需要任何花费就可以直接站在第 0 层和第 1 层上。
  4. 填表顺序: 从左到右填表,逐步计算出 dp[i] 的值。
  5. 返回值: 返回 dp[n] 的值,即到达第 n 个位置时的最小花费。

代码一

class Solution {
public:int minCostClimbingStairs(vector<int>& cost) {int n=cost.size();vector<int> dp(n+1,0);dp[0]=dp[1]=0;for(int i=2;i<=n;++i)dp[i]=min(cost[i-1]+dp[i-1],cost[i-2]+dp[i-2]);return dp[n];}
};

思路二

  1. 状态表示: 定义状态 dp[i] 表示从第 i 个位置出发到达楼顶时的最小花费。
  2. 状态转移方程: 根据题目要求,dp[i] 可以由后两个状态转移而来:dp[i + 1]dp[i + 2]。因此,状态转移方程为 dp[i] = min(dp[i + 1], dp[i + 2]) + cost[i]
  3. 初始化: 为了保证填表的时候不越界,需要初始化最后两个位置的值,结合状态表表示得到 dp[n - 1] = cost[n - 1]dp[n - 2] = cost[n - 2]
  4. 填表顺序: 从右到左填表,逐步计算出 dp[i] 的值。
  5. 返回值: 返回 dp[0]dp[1]中最小的值。

代码二

class Solution {
public:int minCostClimbingStairs(vector<int>& cost) {int n=cost.size();vector<int> dp(n,0);dp[n-1]=cost[n-1],dp[n-2]=cost[n-2];for(int i=n-3;i>=0;--i)dp[i]=min(cost[i]+dp[i+1],cost[i]+dp[i+2]);return min(dp[0],dp[1]);}
};

04.解码方法

题目链接:https://leetcode.cn/problems/decode-ways/

一条包含字母 A-Z 的消息通过以下映射进行了 编码

'A' -> "1"
'B' -> "2"
...
'Z' -> "26"

解码 已编码的消息,所有数字必须基于上述映射的方法,反向映射回字母(可能有多种方法)。例如,"11106" 可以映射为:

  • "AAJF" ,将消息分组为 (1 1 10 6)
  • "KJF" ,将消息分组为 (11 10 6)

注意,消息不能分组为 (1 11 06) ,因为 "06" 不能映射为 "F" ,这是由于 "6""06" 在映射中并不等价。

给你一个只含数字的 非空 字符串 s ,请计算并返回 解码 方法的 总数

题目数据保证答案肯定是一个 32 位 的整数。

示例 1:

输入:s = "12"
输出:2
解释:它可以解码为 "AB"(1 2)或者 "L"(12)。

示例 2:

输入:s = "226"
输出:3
解释:它可以解码为 "BZ" (2 26), "VF" (22 6), 或者 "BBF" (2 2 6) 。

示例 3:

输入:s = "06"
输出:0
解释:"06" 无法映射到 "F" ,因为存在前导零("6" 和 "06" 并不等价)。

提示:

  • 1 <= s.length <= 100
  • s 只包含数字,并且可能包含前导零。

思路

  1. 状态表示: 定义状态 dp[i] 表示字符串中 [0, i] 区间上,一共有多少种编码方法。

  2. 状态转移方程: 分析 i 位置的 dp 值,有两种解码情况:

    • s[i] 在区间 [1, 9] 内时,说明 s[i] 可以单独解码,此时 dp[i] += dp[i - 1]
    • s[i - 1]s[i] 结合后在区间 [10, 26] 内时,说明前两个字符有一种编码方式,此时 dp[i] += dp[i - 2]

    综上所述,状态转移方程为:

    dp[i] = (s[i] != '0' ? dp[i - 1] : 0) + (10 <= stoi(s.substr(i - 1, 2)) <= 26 ? dp[i - 2] : 0)
    
  3. 初始化: 需要初始化前两个位置的值:

    • 初始化 dp[0]
      • s[0] == '0' 时,没有编码方法,结果 dp[0] = 0
      • s[0] != '0' 时,能编码成功,此时 dp[0] = 1
    • 初始化 dp[1]
      • s[1] 在区间 [1, 9] 内时,能单独编码,此时 dp[1] += dp[0]
      • s[0]s[1] 结合后的数在区间 [10, 26] 内时,说明前两个字符有一种编码方式,此时 dp[1] += 1
  4. 填表顺序: 从左往右填表,逐步计算出 dp[i] 的值。

  5. 返回值: 返回 dp[n - 1] 的值,表示在 [0, n - 1] 区间上的编码方法数量。

代码

class Solution {
public:int numDecodings(string s) {int n=s.size();vector<int> dp(n);dp[0]=s[0]!='0';if(n==1) return dp[0];if(s[1]>='1'&&s[1]<='9') dp[1]+=dp[0];int t=(s[0]-'0')*10+s[1]-'0';if(t>=10&&t<=26) dp[1]+=1;for(int i=2;i<n;++i){if(s[i]>='1'&&s[i]<='9') dp[i]+=dp[i-1];int t=(s[i-1]-'0')*10+s[i]-'0';if(t>=10&&t<=26) dp[i]+=dp[i-2];}return dp[n-1];}
};

这篇关于算法沉淀——动态规划之斐波那契数列模型(leetcode真题剖析)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/744633

相关文章

Android 悬浮窗开发示例((动态权限请求 | 前台服务和通知 | 悬浮窗创建 )

《Android悬浮窗开发示例((动态权限请求|前台服务和通知|悬浮窗创建)》本文介绍了Android悬浮窗的实现效果,包括动态权限请求、前台服务和通知的使用,悬浮窗权限需要动态申请并引导... 目录一、悬浮窗 动态权限请求1、动态请求权限2、悬浮窗权限说明3、检查动态权限4、申请动态权限5、权限设置完毕后

0基础租个硬件玩deepseek,蓝耘元生代智算云|本地部署DeepSeek R1模型的操作流程

《0基础租个硬件玩deepseek,蓝耘元生代智算云|本地部署DeepSeekR1模型的操作流程》DeepSeekR1模型凭借其强大的自然语言处理能力,在未来具有广阔的应用前景,有望在多个领域发... 目录0基础租个硬件玩deepseek,蓝耘元生代智算云|本地部署DeepSeek R1模型,3步搞定一个应

Deepseek R1模型本地化部署+API接口调用详细教程(释放AI生产力)

《DeepseekR1模型本地化部署+API接口调用详细教程(释放AI生产力)》本文介绍了本地部署DeepSeekR1模型和通过API调用将其集成到VSCode中的过程,作者详细步骤展示了如何下载和... 目录前言一、deepseek R1模型与chatGPT o1系列模型对比二、本地部署步骤1.安装oll

Spring AI Alibaba接入大模型时的依赖问题小结

《SpringAIAlibaba接入大模型时的依赖问题小结》文章介绍了如何在pom.xml文件中配置SpringAIAlibaba依赖,并提供了一个示例pom.xml文件,同时,建议将Maven仓... 目录(一)pom.XML文件:(二)application.yml配置文件(一)pom.xml文件:首

如何在本地部署 DeepSeek Janus Pro 文生图大模型

《如何在本地部署DeepSeekJanusPro文生图大模型》DeepSeekJanusPro模型在本地成功部署,支持图片理解和文生图功能,通过Gradio界面进行交互,展示了其强大的多模态处... 目录什么是 Janus Pro1. 安装 conda2. 创建 python 虚拟环境3. 克隆 janus

Java使用POI-TL和JFreeChart动态生成Word报告

《Java使用POI-TL和JFreeChart动态生成Word报告》本文介绍了使用POI-TL和JFreeChart生成包含动态数据和图表的Word报告的方法,并分享了实际开发中的踩坑经验,通过代码... 目录前言一、需求背景二、方案分析三、 POI-TL + JFreeChart 实现3.1 Maven

本地私有化部署DeepSeek模型的详细教程

《本地私有化部署DeepSeek模型的详细教程》DeepSeek模型是一种强大的语言模型,本地私有化部署可以让用户在自己的环境中安全、高效地使用该模型,避免数据传输到外部带来的安全风险,同时也能根据自... 目录一、引言二、环境准备(一)硬件要求(二)软件要求(三)创建虚拟环境三、安装依赖库四、获取 Dee

Java导出Excel动态表头的示例详解

《Java导出Excel动态表头的示例详解》这篇文章主要为大家详细介绍了Java导出Excel动态表头的相关知识,文中的示例代码简洁易懂,具有一定的借鉴价值,有需要的小伙伴可以了解下... 目录前言一、效果展示二、代码实现1.固定头实体类2.动态头实现3.导出动态头前言本文只记录大致思路以及做法,代码不进

DeepSeek模型本地部署的详细教程

《DeepSeek模型本地部署的详细教程》DeepSeek作为一款开源且性能强大的大语言模型,提供了灵活的本地部署方案,让用户能够在本地环境中高效运行模型,同时保护数据隐私,在本地成功部署DeepSe... 目录一、环境准备(一)硬件需求(二)软件依赖二、安装Ollama三、下载并部署DeepSeek模型选

vue基于ElementUI动态设置表格高度的3种方法

《vue基于ElementUI动态设置表格高度的3种方法》ElementUI+vue动态设置表格高度的几种方法,抛砖引玉,还有其它方法动态设置表格高度,大家可以开动脑筋... 方法一、css + js的形式这个方法需要在表格外层设置一个div,原理是将表格的高度设置成外层div的高度,所以外层的div需要