Offer必备算法11_斐波那契dp_四道力扣题详解(由易到难)

本文主要是介绍Offer必备算法11_斐波那契dp_四道力扣题详解(由易到难),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

动态规划dp算法原理

①力扣1137. 第 N 个泰波那契数

解析代码1

解析代码2

②力扣面试题 08.01. 三步问题

解析代码

③力扣746. 使用最小花费爬楼梯

解析代码1

解析代码2

④力扣91. 解码方法

解析代码1

解析代码2

本篇完。


动态规划dp算法原理

        动态规划(Dynamic Programming)算法的核心思想是:将大问题划分为小问题进行解决,从而一步步获取最优解的处理算法

        动态规划算法与分治算法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。

        与分治法不同的是,适合于用动态规划求解的问题,经分解得到的子问题往往不是互相独立的(即下一个子阶段的求解是建立在上一个子阶段的解的基础上)。

(除此斐波那契dp外还有其它类型的dp在后面会更新。)

动态规划算法解决问题的分类:

计数

有多少种方式走到右下角 / 有多少种方法选出k个数使得和是sum

求最大值/最小值

从左上角走到右下角路径的最大数字和最长上升子序列长度

求存在性

取石子游戏,先手是否必胜 / 能不能取出k  个数字使得和是 sum

动态规划dp算法一般步骤:

  1. 确定状态表示(dp[ i ] 表示什么,一般以 i 位置为起点或结尾分析,化成子问题)
  2. 状态转移方程(斐波那契数列的状态转移方程为:dp[i] = dp[i-1] + dp[i-2])
  3. 初始化(斐波那契数列初始化可以为dp[0] = 0, dp[1] = 1;)
  4. 填表顺序(斐波那契数列从左往右填)
  5. 返回值(如果斐波那契数列要求是第 n 个斐波那契数,返回dp[ n ] 即可)

①力扣1137. 第 N 个泰波那契数

1137. 第 N 个泰波那契数

 难度 简单

泰波那契序列 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
class Solution {
public:int tribonacci(int n) {}
};

解析代码1

简单的DP,根据题目已经得到状态转移方程了:

class Solution {
public:int tribonacci(int n) {if(n <= 1) // 处理边界return n;vector<int> dp(n+1, 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];}
};

解析代码2

        滚动数组对解法1进行空间上的优化,后面类似的空间优化就不写了,因为笔试没用,面试能讲出来就行。

class Solution {
public:int tribonacci(int n) {if(n <= 1) // 处理边界return n;int a = 0, b = 1, c = 1, d = 1; // 滚动数组思想优化空间for(int i = 3; i <= n; ++i){d = a + b + c;a = b;b = c;c = d;}return d;}
};

②力扣面试题 08.01. 三步问题

面试题 08.01. 三步问题

难度 简单

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

示例1:

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

示例2:

 输入:n = 5
 输出:13

提示:

  1. n范围在[1, 1000000]之间
class Solution {
public:int waysToStep(int n) {}
};

解析代码

和上一题力扣1137. 第 N 个泰波那契数状态转移方程一样,就是要注意数据溢出。

class Solution {
public:int waysToStep(int n) {const int MOD = 1e9 + 7;if(n <= 2) // 处理边界return n;vector<int> dp(n+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];}
};

③力扣746. 使用最小花费爬楼梯

746. 使用最小花费爬楼梯

难度 简单

给你一个整数数组 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
class Solution {
public:int minCostClimbingStairs(vector<int>& cost) {}
};

解析代码1

如果dp[ i ]表示到达i位置的最小花费,则到达dp[ i ] 就有以下两种情况:

  • 先到达dp[ i-1 ] 然后 支付cost[ i-1] 到达 dp[ i ]。
  • 先到达dp[ i-2 ] 然后 支付cost[ i-2] 到达 dp[ i ]。

题意及取两种情况小的那个,得到状态转移方程:dp[i] =min(dp[i-1] + cost[i-1], dp[i-2] + cost[i-2]);

class Solution {
public:int minCostClimbingStairs(vector<int>& cost) {int n = cost.size();vector<int> dp(n + 1, 0); // dp[i]表示到达i位置的最小花费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]; // 楼顶是原数组最后位置的下一个}
};

解析代码2

如果dp[ i ]表示从 i 位置出发,到达楼顶,此时的最小花费,则dp[ i ] 就有以下两种情况:

  • 支付 cost[ i ],往后走一步,从 i + 1的位置出发到终点
  • 支付 cost[ i ],往后走两步,从 i + 2的位置出发到终点

则需要知道 i + 1 和 i + 2 位置的最小花费,及dp [i + 1] 和 dp[i + 2]。所以要从右往左填

则状态转移方程为:dp[i] = min(dp[i+1] + cost[i], dp[i+2] + cost[i]);

class Solution {
public:int minCostClimbingStairs(vector<int>& cost) {int n = cost.size();vector<int> dp(n, 0); // dp[i]表示到达i位置的最小花费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] + cost[i], dp[i+2] + cost[i]);}return min(dp[0], dp[1]);}
};

④力扣91. 解码方法

91. 解码方法

难度 中等

一条包含字母 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 只包含数字,并且可能包含前导零。
class Solution {
public:int numDecodings(string s) {}
};

解析代码1

dp[i]表示字符串中区间,总编码方法

关于 i 位置的编码状况,可以分为下面两种情况:

  • 让 i 位置上的数单独解码成一个字母
  • 让 i 位置上的数与 i - 1 位置上的数结合,解码成一个字母

让 i 位置上的数单独解码成一个字母,就存在 解码成功 解码失败 两种情况:

  • 解码成功:dp[i] = dp[i - 1];([0,i] 上所有解码结果后面填上一个字母即可) 
  • 解码失败:dp[i] = 0;(前面努力都白费)

让 i 位置上的数与 i - 1 位置上的数结合在⼀起,解码成一个字母,也存在 解码成功 解码失败 两种情况:

  • 解码成功:dp[i] = dp[i - 2];    原因同上。
  • 解码失败:dp[i] = 0;(前面努力都白费)
class Solution {
public:int numDecodings(string s) {int n = s.size();vector<int> dp(n, 0); // dp[i]表示字符串中[0,i]区间,总编码方法dp[0] = (s[0] != '0'); // 题目只有0-9,不等于0就能单独编码if(n == 1)return dp[0];if(s[0] != '0' && s[1] != '0') // 两个都能单独编码dp[1] += 1;int y = (s[0] -'0') * 10 + (s[1] - '0');if(y >= 10 && y <= 26) // 和在一起能编码,不含前导0dp[1] += 1;for(int i = 2; i < n; ++i){if(s[i] != '0') // 能单独编码dp[i] += dp[i-1];y = (s[i-1] -'0') * 10 + (s[i] - '0');if(y >= 10 && y <= 26) // 和在一起能编码,不含前导0dp[i] += dp[i-2];}return dp[n-1];}
};

解析代码2

解析代码1是直接初始化,有点麻烦,在其基础上可以添加辅助位置初始化,注意两个点:

  • 辅助结点里面的值要保证后续填表是正确的
  • 下标的映射关系(此题下标有的要减1)
class Solution {
public:int numDecodings(string s) {int n = s.size();vector<int> dp(n+1, 0); // 辅助结点简化dp[0] = 1;dp[1] = (s[0] != '0');for(int i = 2; i <= n; ++i){if(s[i-1] != '0') // 能单独编码dp[i] += dp[i-1];int y = (s[i-2] -'0') * 10 + (s[i-1] - '0');if(y >= 10 && y <= 26) // 和在一起能编码,不含前导0dp[i] += dp[i-2];}return dp[n];}
};

本篇完。

下一篇是链表的OJ,下下篇是动态规划的另一种类型:路径dp。

这篇关于Offer必备算法11_斐波那契dp_四道力扣题详解(由易到难)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

MySQL数据库双机热备的配置方法详解

《MySQL数据库双机热备的配置方法详解》在企业级应用中,数据库的高可用性和数据的安全性是至关重要的,MySQL作为最流行的开源关系型数据库管理系统之一,提供了多种方式来实现高可用性,其中双机热备(M... 目录1. 环境准备1.1 安装mysql1.2 配置MySQL1.2.1 主服务器配置1.2.2 从

Linux kill正在执行的后台任务 kill进程组使用详解

《Linuxkill正在执行的后台任务kill进程组使用详解》文章介绍了两个脚本的功能和区别,以及执行这些脚本时遇到的进程管理问题,通过查看进程树、使用`kill`命令和`lsof`命令,分析了子... 目录零. 用到的命令一. 待执行的脚本二. 执行含子进程的脚本,并kill2.1 进程查看2.2 遇到的

MyBatis常用XML语法详解

《MyBatis常用XML语法详解》文章介绍了MyBatis常用XML语法,包括结果映射、查询语句、插入语句、更新语句、删除语句、动态SQL标签以及ehcache.xml文件的使用,感兴趣的朋友跟随小... 目录1、定义结果映射2、查询语句3、插入语句4、更新语句5、删除语句6、动态 SQL 标签7、ehc

详解SpringBoot+Ehcache使用示例

《详解SpringBoot+Ehcache使用示例》本文介绍了SpringBoot中配置Ehcache、自定义get/set方式,并实际使用缓存的过程,文中通过示例代码介绍的非常详细,对大家的学习或者... 目录摘要概念内存与磁盘持久化存储:配置灵活性:编码示例引入依赖:配置ehcache.XML文件:配置

从基础到高级详解Go语言中错误处理的实践指南

《从基础到高级详解Go语言中错误处理的实践指南》Go语言采用了一种独特而明确的错误处理哲学,与其他主流编程语言形成鲜明对比,本文将为大家详细介绍Go语言中错误处理详细方法,希望对大家有所帮助... 目录1 Go 错误处理哲学与核心机制1.1 错误接口设计1.2 错误与异常的区别2 错误创建与检查2.1 基础

k8s按需创建PV和使用PVC详解

《k8s按需创建PV和使用PVC详解》Kubernetes中,PV和PVC用于管理持久存储,StorageClass实现动态PV分配,PVC声明存储需求并绑定PV,通过kubectl验证状态,注意回收... 目录1.按需创建 PV(使用 StorageClass)创建 StorageClass2.创建 PV

Python版本信息获取方法详解与实战

《Python版本信息获取方法详解与实战》在Python开发中,获取Python版本号是调试、兼容性检查和版本控制的重要基础操作,本文详细介绍了如何使用sys和platform模块获取Python的主... 目录1. python版本号获取基础2. 使用sys模块获取版本信息2.1 sys模块概述2.1.1

一文详解Python如何开发游戏

《一文详解Python如何开发游戏》Python是一种非常流行的编程语言,也可以用来开发游戏模组,:本文主要介绍Python如何开发游戏的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下... 目录一、python简介二、Python 开发 2D 游戏的优劣势优势缺点三、Python 开发 3D

Redis 基本数据类型和使用详解

《Redis基本数据类型和使用详解》String是Redis最基本的数据类型,一个键对应一个值,它的功能十分强大,可以存储字符串、整数、浮点数等多种数据格式,本文给大家介绍Redis基本数据类型和... 目录一、Redis 入门介绍二、Redis 的五大基本数据类型2.1 String 类型2.2 Hash

Java中的.close()举例详解

《Java中的.close()举例详解》.close()方法只适用于通过window.open()打开的弹出窗口,对于浏览器的主窗口,如果没有得到用户允许是不能关闭的,:本文主要介绍Java中的.... 目录当你遇到以下三种情况时,一定要记得使用 .close():用法作用举例如何判断代码中的 input