当前位置: 首页 > news >正文

[dp_1] 使用最小花费爬楼梯 | 解码方法 | 虚拟dp[0]=0

目录

1.使用最小花费爬楼梯

题解

动态规划解法一:

动态规划解法二:

2.解码方法

题解


1.使用最小花费爬楼梯

链接:746. 使用最小花费爬楼梯

给你一个整数数组 cost ,其中 cost[i] 是从楼梯第 i 个台阶向上爬需要支付的费用。

一旦你支付此费用,即可选择向上爬一个或者两个台阶。

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

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

示例 1:

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

题解

本题求达到楼梯顶部的最低花费。

  • 向上爬楼梯需要支付本层的费用,然后可以爬一层或者两层。
  • 可以从下标为 0 或下标为 1 的台阶开始爬楼梯。

注意要求的是爬到楼顶的最低花费,即使到达数组最后一个还需要在往上爬一步加上本层的费用。

动态规划解法一:

1.状态表示

经验+题目要求

向这种一维数组的dp一般经验分为两种:

  1. 以某个位置为结尾,巴拉巴拉(根据题目要求把它替换掉)
  2. 以某个位置为起点,巴拉巴拉

解法一用的是第一种以某个位置为结尾,巴拉巴拉,接下来看如何替换掉巴拉巴拉。

  • 这道题让找达到楼梯顶部的最低花费。
  • 那如果以 i 位置结尾,求的是最少花费。

那我就可以得到这样一个状态表示,dp[i]表示,到达 i 位置时,最少花费


2.状态转移方程

分析状态转移方程的一条总线:

  • 用 i 位置之前或者之后的状态,推导 dp[i] 的值
  • 如i之前状态 dp[i-2]、dp[i-1],i之后状态 dp[i+1],dp[i+2]

如何推导出dp[i]的值呢?

根据最近的一步,来划分问题

  • 如这道题,先到达i-1的位置,从i-1位置花费i-1位置的费用走一步到i,或者可以先到达i-2的位置,花费i-2位置的费用走两步到i。
  • 这是依据 i 位置最近的一步来划分出的两种情况。
  • 因为要求花费最少,所有两种情况种选择最少的。

接下来看这两种情况能不能用之前的状态表示一下。

  • cost[i-1]是定值无法改变,先到达i-1位置也是有一个花费,如果想求i位置最小花费,是不是要先找到i-1位置的最小花费
  • 只要找到i-1位置的最小花费在加上cost[i-1]走一步,就是第一种情况的最小花费。
  • 到达i-1位置最小花费不就是dp[i-1] 表示到达i-1位置,最小花费。
  • 同理i-2也是这样分析的。

然后求的是两种情况的最小值。因此状态转移方程就有了。

3.初始化

  • 确保填表时不能越界
  • dp[0]=dp[1]=0

4.填表顺序

  • 由前面两个位置填后面的位置。
  • 从左往右

5.返回值

  • 结合题目要求,返回到达楼梯最小花费。
  • dp表数组下标为n的地方。所以返回 dp[n]

动态规划解法二:

其实第二种解决就是换了一种状态表示。

1.状态表示

经验+题目要求

以 i 位置为起点,巴拉巴拉

以i位置为起点,然后要去楼顶还要是最小花费

因此 dp[i]表示,从i位置出发,到达楼顶,此时最小花费。

2.状态转移方程

分析状态转移方程的一条总线:

  • 用 i 位置之后的状态,推导 dp[i] 的值
  • 如i之前状态 dp[i-2]、dp[i-1],i之后状态 dp[i+1],dp[i+2]

如何推导出dp[i]的值呢?

  • 根据最近的一步,来划分问题

3.初始化

  • 因为我们是从某一个位置到楼顶,所以dp数组不需要额外在开一个位置。
  • 直接跟原始数组一样大就可以了。
  • 其次我们需要先知道i+1的位置和i+2的位置才能知道dp[i]的值,因此先把最后两个位置初始化
  • dp[n-1]=dp[n-2]=0

4.填表顺序

  • 知道后面两个位置的值,就可以得到前面的值,因此从右往左

5.返回值

  • 我们最开始要么是从0开始,要是是从1开始。所以返回的是dp[0],dp[1]中的最小值。
class Solution {
public:
    int minCostClimbingStairs(vector<int>& cost) 
    {
        //dp
        //初始化
        //方程
        //返回

        int n=cost.size();
        vector<int> dp(n);//以i出发,到达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]+min(dp[i+1],dp[i+2]);
        }
        return min(dp[0],dp[1]);
    }
};

2.解码方法

链接:91. 解码方法

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

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

然而,在 解码 已编码的消息时,你意识到有许多不同的方式来解码,因为有些编码被包含在其它编码当中("2""5""25")。

例如,"11106" 可以映射为:

  • "AAJF" ,将消息分组为 (1, 1, 10, 6)
  • "KJF" ,将消息分组为 (11, 10, 6)
  • 消息不能分组为 (1, 11, 06) ,因为 "06" 不是一个合法编码(只有 "6" 是合法的)。

注意,可能存在无法解码的字符串。

给你一个只含数字的 非空 字符串 s ,请计算并返回 解码 方法的 总数 。如果没有合法的方式解码整个字符串,返回 0

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

示例 1:

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

示例 2:

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

题解

1.状态表示

  • 经验+题目要求
  • 以 i 位置为结尾,巴拉巴拉。
  • 接下来根据题目要求替换巴拉巴拉。
  • 题目要求求s字符串有多少种解码方法。是不是就从从开始到结尾的解码总数。

那dp[i]就可以这样表示。dp[i]表示,以 i 位置为结尾时,解码方法的总数。

2.状态转移方程

  • 根据i状态最近的一步,来划分问题(只考虑当下的事情,做好当下

最近一步就是解码到i位置的时候,解码到i位置有两种情况

  • i位置单独解码
  • i-1和i位置合在一起解码。

因为是以i位置为结尾的,所有i+1位置还没有到,暂时不考虑。

  • 但是解码并不是想解码就解码,必须要符合条件,否则不能解码。
  • 所有 单独解码 和 合在一起解码 都有成功或者失败的可能。
  • 这时就要我们进行一个决策 if 之后,才能对 dp[i] 进行对上一状态的加等

3.初始化

  • 因为会用到i-1和i-2所以要对0和1初始化。

4.填表顺序

  • 填dp[i]要知道dp[i-1]和dp[i-2]的位置,所以从左到右(因为是以 i 结束)

5.返回值

  • dp[i]表示以 i 位置为结尾时,解码方法的总数
  • 题目要求求整个字符串所有解码方案,所以返回的是dp[n-1]。
class Solution {
public:
    int numDecodings(string s) 
  {
    if(s[0]=='0') return 0;//处理 特殊情况
      
        int n = s.size();
        vector<int> dp(n);
        dp[0]=1;
        //处理边界情况
        if(n == 1) return 1;

        if(s[0] != '0' && s[1] != '0')
            dp[1] += 1;
        int tmp = (s[0] - '0') * 10 + s[1] - '0'; //前两个位置表示的数
        if(tmp >= 10 && tmp <= 26)
            dp[1] += 1;


        for(int i = 2; i < n; ++i)
        {
            if(s[i] != '0') dp[i] += dp[i - 1];//处理单独编码的情况
            int tmp = (s[i - 1] - '0') * 10 + s[i] - '0';//处理合在一起编码的情况
            if(tmp >= 10 && tmp <= 26)
                dp[i] += dp[i - 2];
        } 
        return dp[n-1];
    }
};

之前写的dp代码都比较短,但是这里的dp初始化为什么这么长并且

  • 部分初始化代码和填表中代码类似,有没有可能写在一起?
  • 使代码编码简洁,是有的。

细节问题:

  • 做dp问题的时候,会经常处理比较繁琐的边界情况以及初始化。
  • 为了能更好的处理这些情况,对于一维数组我们可以把整个数组统一往后移动一位
  • 也就是数组多开一个位置的技巧。

处理边界问题以及初始化问题的技巧

  • 数组多开一个位置

之前旧dp表中的位置的值要在新的dp表中对应位置往后放一个。

  • 多出来的位置我们称为虚拟位置
  • 多出来这个位置的作用,前面在旧的dp表要初始化0和1位置

在新dp表中虽然也初始化0和1的位置,但是确是方便了不少。

  • 之前旧dp表初始化1的位置非常麻烦。
  • 现在旧表中1的位置跑到新dp表中1填表的下标里面了。
  • 我在新dp表中填表中就把旧dp表中1的位置干掉了。

这样就非常爽了~

但是却有两个注意事项:

  • 虚拟节点里面的值,要保证后面填表是正确的
  • 下标的映射关系

虚拟节点里面的值,要保证后面填表是正确的

比如新dp表中,填表时的 dp[2] = dp[1] + dp[0]

  • dp[1]是不会错误的因为它的初始化是和旧dp[0]是一样的。
  • 但是dp[0]是我们构建出来的,它里面值存放多少是不是就会影响到dp[2]的值。
  • 一般情况下,这个虚拟节点的值存的是 0 ,但是这道题就不一样了,dp[0]里面存0是不正确的。
  • 求dp[2]如果用到dp[0],是不是就是1和2的位置合在一起能解码成功,然后我才加上dp[0,

如果dp[0]是0那不就是少加了一种情况吗。因此这个dp[0]填1。

  • 使用虚拟节点的时候,可以进行一个简单的分析不要想当然
  • 总体来说就是具体问题具体分析!看虚拟节点的值到底填几。

下标的映射关系

  • 在新dp表中,初始化dp[1]的时候,看的是s[0]这个位置能否解码成功,对应就是s[1-1] != ‘0’。
  • 因为我们多加个一个位置,下标统一往后移动一位

处理的原位置s[i] 就为 s[i-1] 了

class Solution {
public:
    int numDecodings(string s) 
    {
        if(s.empty() || s[0] == '0') return 0; //!!!!!!首位为0,就直接返回0

        //dp
        //虚拟 节点
        int n=s.size();
        vector<int> dp(n+1);
        dp[0]=1; //初始1,因为 要实现 26位字母+
        dp[1]=1;

        //虚拟化节点 下标映射dp i //s i-1

        if(n==1) return dp[1];
        for(int i=2;i<=n;i++)
        {
            if(s[i-1]!='0') dp[i]+=dp[i-1];
            int tmp=(s[i-2]-'0')*10+(s[i-1]-'0');
            if(tmp>=10 && tmp<=26) dp[i]+=dp[i-2];
        }
        return dp[n];
    }
};
  • if(s.empty() || s[0] == '0') return 0; //!!!!!!首位为0,就直接返回0
  • dp[0]=1; //初始1,因为 要实现 26位字母+
  • 虚拟化节点 下标映射 原s[i]--->s[i-1]
http://www.dtcms.com/a/107643.html

相关文章:

  • 【输入某年某日,判断这是这一年的第几天】
  • 中小企业商标管理新选择:启服云。
  • Conmon lisp Demo
  • 如何在服务器里备份文件或系统
  • 基于NebulaGraph构建省市区乡镇街道知识图谱(二)
  • Bugku-眼见非实
  • 5.模型训练-毕设篇
  • HTML5手写签名板项目实战教程
  • linux -- php 扩展之xlswriter
  • DAY46 动态规划Ⅸ 股票问题Ⅱ
  • 机构数据服务
  • 搜索工具Everything下载安装使用教程(附安装包)
  • 网络安全的挑战与防护策略
  • Excel时间类型函数(包括today、date、eomonth、year、month、day、weekday、weeknum、datedif)
  • 大模型-提示词(Prompt)最佳实践
  • 【零基础入门unity游戏开发——2D篇】SpriteEditor图片编辑器
  • Unity 渲染流水线
  • 什么是编译和反编译
  • 【Python】Python 环境 + Pycharm 编译器 官网免费下载安装(图文教程,新手安装,Windows 10 系统)
  • 智能矢量化(地质类栅格图像)
  • python实战案例:销售数据BI动态分析仪表板
  • 今日行情明日机会——20250402
  • 任务堆积导致 OOM(内存溢出)
  • 08-MySQL InnoDB锁的基本类型
  • 【前端】电脑初始安装软件工具
  • 【Linux】内核驱动学习笔记(一)
  • 【论文笔记】DeepSeek-R1 技术报告
  • java虚拟机---JVM
  • python实战案例:财务凭证数据分析和生成报告
  • .net 6 + vue3中使用SignaIR实现双向通信功能