【动态规划】斐波那契数列模型 {动态规划解决哪些问题?使用动态规划的前提;动态规划的解题思路;动态规划的优化方案}

一、经验总结

动态规划顾名思义就是状态的动态表示,而动态是通过规划得到,动态是在不同阶段中的状态。

动态规划解决哪些问题?
动态规划用于解决子问题重复计算的大问题。复杂的问题往往分多个阶段,而不同阶段包含不同的状态,各阶段可能相互联系,若暴力计算会出现很多的重复计算,浪费计算资源。

使用动态规划的前提
使用动态规划的一个前提是问题无后效性
后效性是指当前的阶段状态即受到过去的阶段状态影响,而过去阶段的状态也会收到现在阶段状态的影响。如下图所示:
在这里插入图片描述
无后效性是指当前的阶段状态只受过去阶段状态的影响,且当前阶段状态并不影响过去阶段的状态。如下图所示:
在这里插入图片描述
虚线表示可能受到之间多个状态的影响。

动态规划题型的解题思路

  1. 状态表示:dp[i]的含义是什么。通过解题经验和题目要求得到,一般有以下两个方向:
    • 以i位置为起点
    • 以i位置为终点
  2. 状态转移方程:dp[i]怎么求。根据距离i位置最近的一步划分问题,用之前或之后的状态推导出dp[i]的值。
  3. 初始化:为了保证填表的时候不越界,预处理边界情况
  4. 填表顺序:保证填写当前状态的时候,所需要的状态已经计算过了
  5. 返回值:题目要求+状态表示

提示:同一个动态规划问题,有时可以采用多种不同的状态表示(如2.3)。状态表示不同,其余的4点也不同。而有些问题的状态表示可能只适用于其中的一种,要因题而议选择可行的方案。

动态规划的优化方案

  1. 空间优化:采用滚动数组的方案,只需要记录求当前状态时所需要的状态即可。
  2. 虚拟节点:设置虚拟节点,方便处理边界情况。需要注意以下两点:
    • 虚拟节点的初始值,要保证后续填表的正确性。
    • dp表与原始表的下标映射关系

二、相关编程题

2.1 第N个泰波那契数

题目链接

1137. 第 N 个泰波那契数 - 力扣(LeetCode)

题目描述

在这里插入图片描述

算法原理

在这里插入图片描述

编写代码

//动态规划
class Solution {
public:
    int tribonacci(int n) {
        vector<int> dp(n<=2? 3:n+1); //创建dp表
        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]; //返回值
    }
};

//空间优化
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;
        for(int i = 3; i <= n; ++i)
        {
            d = a+b+c;
            a=b, b=c, c=d; //滚动数组
        }
        return d;
    }
};

2.2 三步问题

题目链接

面试题 08.01. 三步问题 - 力扣(LeetCode)

题目描述

在这里插入图片描述

算法原理

在这里插入图片描述

编写代码

//经典动态规划
class Solution {
public:
    int waysToStep(int n) {
        const int MOD = 1e9+7;
        vector<long> dp(n<=3? 4:n+1); //创建dp表
        dp[1]=1, dp[2]=2, dp[3]=4; //初始化
        for(int i = 4; i <= n; ++i) //填表
        {
            dp[i] = dp[i-1]+dp[i-2]+dp[i-3];
            dp[i]%=MOD;
        }
        return dp[n]; //返回
    }
};

//空间优化版本
class Solution {
public:
    int waysToStep(int n) {
        if(n==1 || n==2) return n;
        if(n==3) return 4;
        const int MOD = 1e9+7;
        long a=1, b=2, c=4, d;
        for(int i = 4; i <= n; ++i)
        {
            d = a+b+c;
            d %= MOD;
            a=b, b=c, c=d; //滚动数组
        }
        return d;
    }
};

2.3 使用最小花费爬楼梯

题目链接

746. 使用最小花费爬楼梯 - 力扣(LeetCode)

题目描述

在这里插入图片描述

算法原理

在这里插入图片描述

编写代码

//解法一:以i位置为终点
class Solution {
public:
    int minCostClimbingStairs(vector<int>& cost) {
        int n = cost.size();
        vector<int> dp(n+1); //创建dp表
        dp[0] = dp[1] = 0; //初始化
        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]; //返回值

    }
};

//解法二:以i位置为起点
class Solution {
public:
    int minCostClimbingStairs(vector<int>& cost) {
        int n = cost.size();
        vector<int> dp(n); //创建dp表
        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]); //返回值
    }
};

2.4 解码方法

题目链接

91. 解码方法 - 力扣(LeetCode)

题目描述

在这里插入图片描述

算法原理

在这里插入图片描述

编写代码

//经典动态规划
class Solution {
public:
    int numDecodings(string s) {
        int n = s.size();
        if(n==1) return CheckCode(s[0])? 1:0;
        vector<int> dp(n); //dp[i]表示以i位置为结尾的解码方法
        dp[0] = CheckCode(s[0])? 1:0; //初始化
        dp[1] = CheckCode(s[1])? dp[0]:0;
        dp[1] += CheckCode(s[0], s[1])? 1:0;
        for(int i = 2; i < n; ++i)
        {
            dp[i] = CheckCode(s[i])? dp[i-1]:0;
            dp[i] += CheckCode(s[i-1], s[i])? dp[i-2]:0;
        }
        return dp[n-1];
    }

    bool CheckCode(char a, char b = 0)
    {
        if(b == 0)
        {
            return a>='1' && a<='9';
        }
        else
        {
            int tmp = (a-'0')*10+b-'0';
            return tmp>=10 && tmp<=26;
        }
    }
};

//添加虚拟节点,方便处理边界问题
class Solution {
public:
    int numDecodings(string s) {
        int n = s.size();  
        vector<int> dp(n+1); //dp[i]表示以i位置为结尾的解法方法
        dp[0] = 1; //注意虚拟节点的初始值
        dp[1] = CheckCode(s[0])? 1:0; //初始化
        for(int i = 2; i <= n; ++i)
        {
            dp[i] = CheckCode(s[i-1])? dp[i-1]:0;
            dp[i] += CheckCode(s[i-2], s[i-1])? dp[i-2]:0;
        }
        return dp[n];
    }
};
  • 6
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

芥末虾

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值