动态规划课堂1-----斐波那契数列模型

目录

动态规划的概念:

动态规划的解法流程:

[题目: 第 N 个泰波那契数](#题目: 第 N 个泰波那契数)

解法(动态规划)

代码:

优化:

题目:最小花费爬楼梯

解法(动态规划)

解法1:

解法2:

题目:解码方法

解法(动态规划)

结语:


动态规划:斐波那契数列模型

动态规划的概念:

动态规划(英语:Dynamic programming,简称 DP),是一种在数学、管理科学、计算机科学、经济学和生物信息学中使用的,通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。动态规划常常适用于有重叠子问题和最优子结构性质的问题。

动态规划的解法流程:

1.状态表示

dp问题的基础,自己要确定dp表每一个下标值的含义,这是用动态规划解决问题的第一步,只有把这一步确定了再去推出下面的状态转移方程,第一第二步完成后那么dp问题就已经解决了99%因为剩下的345就是处理边界和一些细节问题。

2.状态转移方程

推出状态转移方程可以说是dp问题最难的一步,如果在选定的状态表示下推不出状态转移方程,那么可能要换一个状态表示,因为状态表示可能是错误的。

3.初始化

一般初始化dp0和dp1 .

4.填表顺序

一般有从左向右和从右先左,这取决于题目(覆盖问题)。

5.返回值

最后的返回值(不一定是dpn).

由于是算法只讲知识点是远远不够的,故下面我会用例题来帮助大家理解(例题的链接会在最后给出)。

到这一些基本概念就讲解完毕下面开始用题目要带友友更加深入学习。

题目: 第 N 个泰波那契数

题目链接1137. 第 N 个泰波那契数

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

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

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

解法(动态规划)

  1. 状态表示:

根据题目来推出状态表示,后面的大部分题目是要经验+题目来推出的

这道题可以「根据题目的要求」直接定义出状态表示:

dpi 表示:第i 个泰波那契数的值。

2.状态转移方程

dpi = dpi - 1 + dpi - 2 + dpi - 3

3.初始化

从我们的递推公式可以看出, dpi 在 i = 0 以及i = 1 的时候是没有办法进行推导的,因 为dp-2 或dp-1 不是⼀个有效的数据。因此我们需要在填表之前,将0, 1, 2 位置的值初始化。题目中已经告诉我们dp0 = 0, dp1 = dp2 = 1 。(处理一些边界问题)

4.填表顺序

毫无疑问是「从左往右」。

5.返回值:

应该返回dpn 的值。

代码:

dp问题的代码编写流程一般比较固定分为1.创建dp表,2.初始化,3.填表,4.返回值.

最上面两个if用来解决边界问题。

java 复制代码
class Solution {
    public int tribonacci(int n) {
        //1.创建dp表
        //2.初始化
        //3.填表
        //4.返回值
        int[] dp = new int[n + 1];
        if(n == 0){
            return 0;
        }
        if(n == 1 || n == 2){
            return 1;
        }
        dp[0] = 0;dp[2] = dp[1] = 1;

        for(int i = 3;i <= n;i++){
            dp[i] = dp[i - 3] + dp[i - 2] + dp[i - 1];
        }
        return dp[n];

    }
}

优化:

下面动图来自力扣。

一般是利用滚动数组优化(可以是一个小数组也可以是几个变量)

代码如下:

其实就是把表变成几个变量把空间复杂度降低到O(1)。

java 复制代码
class Solution {
    public int tribonacci(int n) {
        //1.创建dp表
        //2.初始化
        //3.填表
        //4.返回值
        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;

    }
}

类似题:和上面那题类似给大家练手。

三步问题

参考代码如下:

其中dpi 表示:到达i位置时,⼀共有多少种方法。

通过分析知道第i步的方法为前三步方法的总和故状态转移方程为dpi = dpi - 1 + dpi - 2 + dpi - 3

java 复制代码
class Solution {
    public int waysToStep(int n) {
        //1.创建dp表
        //2.初始化
        //3.填表
        //4.返回值
        //处理一下边界问题
        int MOD = (int)1e9 + 7;
        if(n == 1 || n == 2){
            return n;
        }
        if(n == 3){
            return 4;
        }
        int[] dp = new int[n + 1];
        dp[1] = 1;dp[2] = 2;dp[3] = 4;
        for(int i = 4;i <= n;i++){
            dp[i] = ((dp[i - 3] + dp[i - 2]) % MOD+ dp[i - 1]) % MOD;
        } 
        return dp[n];
    }
}

题目:最小花费爬楼梯

题目链接:使用最小花费爬楼梯

注意这里的顶部不是数组的最后一个位置,而是在数组最后一个位置再后面一个。

解法(动态规划)

解法1:

  1. 状态表示:

这道题可以根据「经验+题⽬要求」直接定义出状态表示:dpi 表示:到达i 位置时的最小花费。(注意:到达i 位置的时候, i 位置的钱不需要算上)。

2.状态转移方程

根据最近的⼀步,分情况讨论:

(1)先到达i - 1 的位置,然后⽀付costi - 1 ,接下来⾛⼀步⾛到i 位置: dpi - 1 + csoti - 1

(2)先到达i - 2 的位置,然后⽀付costi - 2 ,接下来⾛⼀步⾛到i 位置: dpi - 2 + csoti - 2

3.初始化

从我们的递推公式可以看出,我们需要先初始化i = 0 ,以及i = 1 位置的值。容易得到dp0 = dp1 = 0 ,因为不需要任何花费,就可以直接站在第0 层和第1 层上。

4.填表顺序

根据「状态转移方程」可得,遍历的顺序是「从左往右」。

5.返回值

根据「状态表⽰以及题目要求」,需要返回dpn 位置的值。

代码:

java 复制代码
class Solution {
    public int minCostClimbingStairs(int[] cost) {
        //1.创建dp表
        //2.初始化
        //3.填表
        //4.返回值
        int n = cost.length;
        int[] dp = new int[n + 1];
        dp[0] = 0;dp[1] = 0;
        for(int i = 2;i <=n;i++){
            dp[i] = Math.min((dp[i - 2] + cost[i - 2]),(dp[i - 1] + cost[i - 1]));
        }
        return dp[n];
    }
}

解法2:

解法一和解法二的区别就是状态表示不一样,这样再描述一个解法二是为了告诉大家解法不一定只有一种选定状态表示去试一下状态转移方程(不要怕错)。

  1. 状态表示:

dpi 表示:从i 位置出发,到达楼顶,此时的最小花费。

2.状态转移方程:

根据最近的⼀步,分情况讨论:

(1)支付costi ,往后走⼀步,接下来从i + 1 的位置出发到终点: dpi + 1 + costi

(2)支付costi ,往后走⼀步,接下来从i + 1 的位置出发到终点: dpi + 1 + costi

我们要的是最小花费,因此dpi = min(dpi + 1, dpi + 2) + costi

剩下三步我就不多赘述。

代码如下:

java 复制代码
class Solution {
    public int minCostClimbingStairs(int[] cost) {
        //1.创建dp表
        //2.初始化
        //3.填表
        //4.返回值
        int n = cost.length;
        int[] dp = new int[n];
        dp[n - 1] = cost[n- 1];dp[n - 2] = cost[n - 2];
        for(int i = n - 3;i >= 0;i--){
            dp[i] = cost[i] + Math.min(dp[i + 1],dp[i + 2]);
        }
        return Math.min(dp[0],dp[1]);

    }
}

题目:解码方法

解法(动态规划)

  1. 状态表示:

根据以往的经验,对于⼤多数线性dp ,我们经验上都是「以某个位置结束或者开始」做文章,这 ⾥我们继续尝试「⽤i位置为结尾」结合「题⽬要求」来定义状态表⽰。dpi 表⽰:字符串中0,i 区间上,⼀共有多少种编码⽅法。

2.状态转移方程

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

(1)让i 位置上的数单独解码成⼀个字⺟。

(2)让i 位置上的数与i - 1 位置上的数结合,解码成⼀个字⺟。

让i位置上的数单独解码成⼀个字⺟,就存在「解码成功」和「解码失败」两种情况:

(1)当i位置上的数单独解码成⼀个字⺟。

解码成功:当i 位置上的数在1, 9 之间的时候,说明i 位置上的数是可以单独解 码的,那么此时0, i 区间上的解码⽅法应该等于0, i - 1 区间上的解码方法。因为0, i - 1 区间上的所有解码结果,后⾯填上⼀个i 位置解码后的字⺟就 可以了。此时dpi = dpi - 1

解码失败:当i 位置上的数是0 的时候,说明i 位置上的数是不能单独解码的,那么 此时0, i 区间上不存在解码⽅法。因为i 位置如果单独参与解码,但是解码失败了,那么前⾯做的努⼒就全部⽩费了。此时dpi = 0 。

(2)让i 位置上的数与i - 1 位置上的数结合,解码成⼀个字⺟。

解码成功:当结合的数在10, 26 之间的时候,说明i - 1, i 两个位置是可以 解码成功的,那么此时0, i 区间上的解码⽅法应该等于0, i - 2 区间上的解码 ⽅法,原因同上。此时dpi = dpi - 2

解码失败:当结合的数在0, 927 , 99 之间的时候,说明两个位置结合后解 码失败(这⾥⼀定要注意00 01 02 03 04 ......这⼏种情况),那么此时0, i 区间上的解码⽅法就不存在了,原因依旧同上。此时dpi = 0 。

综上所述: dpi 最终的结果应该是上⾯四种情况下,解码成功的两种的累加和(因为我们关⼼的是解码⽅法,既然解码失败,就不⽤加⼊到最终结果中去),因此可以得到状态转移⽅程( dpi 默认初始化为0 ):

(1)当si 上的数在1, 9 区间上时: dpi += dpi - 1

(2)当si - 1 与si 上的数结合后,在10, 26 之间的时候: dpi += dpi - 2

如果上述两个判断都不成⽴,说明没有解码⽅法, dpi 就是默认值0 。

3.初始化

(1)当s0 != '0' 时,能编码成功, dp0 = 1 初始化dp1

(2)当s11,9 之间时,能单独编码,此时dp1 += dp0 (原因同上, dp1 默认为0 )

(3)当s0 与s1 结合后的数在10, 26 之间时,说明在前两个字符中,⼜有⼀种 编码⽅式,此时dp1 += 1 。

4.填表顺序

毫⽆疑问是「从左往右」

5.返回值

应该返回dpn - 1 的值,表⽰在0, n - 1 区间上的编码⽅法。

代码:

java 复制代码
class Solution 
{
    public int numDecodings(String ss) 
    {
        // 1. 创建 dp 表 
        // 2. 初始化 
        // 3. 填表 
        // 4. 返回值 
        int n = ss.length();
        char[] s = ss.toCharArray();
        int[] dp = new int[n];
        if(s[0] != '0') dp[0] = 1; // 初始化第⼀个位置 
        if(n == 1) return dp[0]; // 处理边界情况 
        // 初始化第⼆个位置 
        if(s[1] != '0' && s[0] != '0') dp[1] += 1;
        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] != '0') dp[i] += dp[i - 1];
         // 处理第⼆种情况 
            int tt = (s[i - 1] - '0') * 10 + s[i] - '0';
            if(tt >= 10 && tt <= 26) dp[i] += dp[i - 2];
        }
        return dp[n - 1];
    }
}

优化:

添加辅助位置初始化

可以在最前⾯加上⼀个辅助结点,帮助我们初始化。使⽤这种技巧要注意两个点:

(1)辅助结点⾥⾯的值要保证后续填表是正确的;

(2)下标的映射关系

使用这种方式可以减少初始化的负担dp1就可以不用初始化,且不用考虑边界问题,因为我们的dp数组会开辟n+1,里面原本的数据都向后移动一位,dp0一般是0或者1(具体看题)。

代码如下:

java 复制代码
class Solution {
    public int numDecodings(String s) {
        //1创建dp表
        //2初始化
        //3填表
        //4返回值
        char[] ss = s.toCharArray();
        int n = s.length();
        int[] dp = new int[n + 1];
        dp[0] = 1;
        if(ss[0] != '0'){
            dp[1] = 1;
        }
        for(int i = 2;i <= n;i++){
            if(ss[i - 1] != '0'){
                dp[i] += dp[i - 1];
            }
            int tt = (ss[i - 2] - '0') * 10 + ss[i - 1] - '0';
            if(tt >= 10 && tt <= 26){
                dp[i] += dp[i - 2];
            }
        }
        return dp[n];
    }
}

结语:

其实写博客不仅仅是为了教大家,同时这也有利于我巩固自己的知识点,和一个学习的总结,由于作者水平有限,对文章有任何问题的还请指出,接受大家的批评,让我改进,如果大家有所收获的话还请不要吝啬你们的点赞收藏和关注,这可以激励我写出更加优秀的文章。

相关推荐
2501_931803756 分钟前
线性筛(欧拉筛):从原理到应用
算法
fengxin_rou8 分钟前
深入理解Java类加载机制:从原理到实战详解
java·开发语言
糖果店的幽灵10 分钟前
Spring AI 从入门到精通-Prompt 工程
java·spring·prompt
ysu_031410 分钟前
leetcode数据结构与算法5~7:链表双指针与二级指针
数据结构·学习·算法·leetcode·链表
小江的记录本12 分钟前
【Spring全家桶】Spring Cloud 2023.0.x:配置中心:Nacos Config、Apollo(附《思维导图》+《面试高频考点清单》)
java·spring boot·后端·python·spring·spring cloud·面试
小欣加油13 分钟前
leetcode542 01矩阵
数据结构·c++·算法·leetcode·矩阵·bfs
weixin_4083180414 分钟前
2026年医疗直播行业趋势报告:技术方向、监管变化与市场格局
java·大数据·人工智能
linge_sun14 分钟前
SpringAI 五步提示词大法:构建高效 AI 提示词
java·人工智能·ai编程
huipeng92619 分钟前
企业级微服务开发实战(三):公共模块设计与统一规范封装
java·spring boot·spring cloud·微服务·架构·系统架构·php
我登哥MVP25 分钟前
Spring Boot 从“会用”到“精通”:参数绑定体系全景
java·spring boot·spring·servlet·maven·intellij-idea·mybatis