动态规划——详解斐波那契数列模型(爬楼梯最小花费和解码方法)

         一、快速上手dp表的建立和求得状态转移方程

       我将此类动态规划问题称为建立dp表问题,方法较为固定,只要走好:

1:建立dp表;

2:初始化(是为了考虑在填表阶段不会出现越界的情况,并且初始化的是dp表的值);

3:填表;

4:返回值(只要主意好要求返回的是表中最后一个数据还是前一个数据就ok);

这四步的总结适用于各类斐波那契额数列模型的问题,我将用以下两个问题分别用两种解题思路来进行剖析。

      对于此题,我们来分析他的状态,是让求最后下标为i的位置的总花费。

   第三步,我们来书写状态转移方程:用之前或之后的状态来推导出dp[i]的值,从而得到这个状态转移方程。

    理论上,只要我们能求出这类题型的所有状态转移方程,我们就可以求出关于这类所有的解决方案。

   第四步,其次就是初始化 填表顺序,以及返回值。

    该题通过观察状态转移方程,求出dp[i]就要求出dp[i-1]和dp[i-2]中的最小值,那么就是要从左往右来求出dp[i]的结果。

    最后就是返回值,因为该题说明是求小标为n的最小花费,那么就是这个dp表的最后一个下标,那么就是返回 dp[n];

    接下来就是代码的编写情况:

class Solution {
public:
    int minCostClimbingStairs(vector<int>& cost) {
        //1.建立dp表
        //2.初始化
        //3.填表
        //4.返回值
        int n=cost.size();
        vector<int> dp(n+1);

        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个位置的最小花费。

   1、从后往前思考的逆向思维,求出在从i个位置出发所用的花费最小,然后就往前推出,在dp[0]或者dp[1]位置时所需要的总花费的最小数。

         现在,我们假设是从第i个位置出发一直到终点的花费最小,那么出发时 ,我们先支付cost[i]的费用,一直到i+1的位置或者到i+2的位置出发到终点,所花费的最小费用就是min(dp[i+1],dp[i+2]);

         综上:求出dp[i]:从i位置出发,到达楼顶,此时最小花费是:dp[i]=cost[i]+min(dp[i+1],dp[i+2]);

     然后就是对整个数组的最后两个值进行初始化,并且求得返回值,因为我们是从后往前计算,求得总花费最小,那么就返回min(dp[0],dp[1]);

class Solution {
public:
    int minCostClimbingStairs(vector<int>& cost) {
        //1.建立dp表
        //2.初始化
        //3.填表
        //4.返回值
        int n=cost.size();
        vector<int> dp(n);//不用考虑从楼顶出发的费用

        //初始化最后两个位置的值
        dp[n-1]=cost[n-1],dp[n-2]=cost[n-2];

        //填表的顺序是倒着来的,从右往左,从第n-3个位置开始填
        for(int i=n-3;i>=0;i--)
        {
            dp[i]=cost[i]+min(dp[i+1],dp[i+2]);
        }
        return min(dp[0],dp[1]);
    }
};

二、(91、)解码方法

我们还是一样,首先观察题意,进行

1、状态表示;2、状态转移表达式的书写;3、初始化;4、确定填表顺序;5、确定返回值

    

class Solution {
public:
    int numDecodings(string s) {
        //1.建立dp表
        //2.初始化//就是为了不在填表的时候越界 会用到v[i-1] 和 v[i-2]的值 v[1] 有一种情况就是都能单独解码,但是不能同时解码,算一种情况
        //3.填表
        //4.返回值
        int n=s.size();
        vector<int> v(n);

        //初始化 一直以来初始化都是初始化v[1]与v[0]这两个值
        v[0]=s[0]!='0';
        if(n==1)
        return v[0];
        //如果第一个位置和第二个位置都能单独解码
        if(s[0]!='0'&&s[1]!='0')
        v[1]+=1;
        //如果第一和第二个位置能组合解码
        int t=(s[0]-'0')*10+s[1]-'0';
        if(t>=10&&t<=26)
        v[1]+=1;

        for(int i=2;i<n;i++)
        {
            //单独解码
            if(s[i]!='0')
            v[i]+=v[i-1];
            //组合解码
            int t=(s[i-1]-'0')*10+s[i]-'0';
            if(t>=10&&t<=26)
            v[i]+=v[i-2];
        }
        return v[n-1];
    }
};

但是这里能发现,代码量过于冗余,在初始化第一和第二个位置组合解码或者单独解码时,代码与for循环中代码保持一至,我们考虑是否可以把他优化一下,放入for循环中解决代码的冗余。

那么,我们就在开辟数组的时候,将字符串的所有下标都往后移动一位,让数组第0下标成为一个虚拟位置 

class Solution {
public:
    int numDecodings(string s) {
        //优化
        int n=s.size();
        vector<int> dp(n+1);

        dp[0]=1;
        dp[1]=s[1-1]!='0';

        for(int i=2;i<=n;i++)
        {
            if(s[i-1]!='0')
            dp[i]+=dp[i-1];
            int t=(s[i-2]-'0')*10+s[i-1]-'0';
            if(t>=10&&t<=26)
            dp[i]+=dp[i-2];
        }
        return dp[n];
    }
};

这样的代码确实,能让人眼前一亮,如果你有需要补充的,欢迎留言~ 

  • 34
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值