动态规划篇-01:爬楼梯

本文为力扣70:爬楼梯的详细解析。

虽然这道题的标签是“简单”,但是只有简单的题才能让我们专注于这类题的解题框架上。

一般来说动态规划会有三种解法:暴力解法、使用了备忘录自上而下的递归解法、使用了数组的自下而上的迭代解法。接下来我会对这三种解法逐一演示

70:爬楼梯

根据上文 动态规划篇-00:解题思想与框架 首先我们要明确 [状态转移方程],这样我们就能写出最基础的暴力解法了。

状态转移方程

思考状态转移方程的思路:base case → 明确状态 → 明确路径 → 定义dp函数

base case

在此题中,最小子问题或者说是边界条件就是 “楼梯阶梯数为1或者2的时候”。这个边界问题是根据题意 “你每次可以爬1或2个台阶” 得来的

明确状态

“原问题和子问题中会变化的变量”

此处的 “选择” 为 爬到楼顶的方法

确定选择

“导致状态变化的行为”

此处 “状态” 为楼梯的阶数。正是因为楼梯阶数的变化,导致“爬到楼顶的方法”产生了变化。

定义dp函数

根据题意,自然而然定义 dp(n) 为 “爬上n阶台阶的方法”

我们回想上一篇文章,“[分解问题]思路扩展一下就是动态规划算法”。那么dp(n)的子问题是什么?

我们最后一步是爬上第n阶阶梯,这一步之前我们需要决定是爬1步到n层(dp(n-1))还是爬两步到n层(dp(n-2))。

于是我们根据子问题和上述分析得出了状态转移方程:

状态转移方程中的 “状态转移” 在此处的表现就是:f(n)这个状态可以由 f(n-1) 和 f(n-2) 转移而来

有了状态转移方程就能写出暴力解法了

暴力递归

class Solution {
    public int climbStairs(int n) {   
        return dp(n);
    }
    //定义一个方法dp,表示:有多少种方法爬上n阶阶梯
    public int dp(int n){
        //明确base case
        if(n == 1 || n == 0){
            return 1;
        }
        //写出状态转移方程
        return dp(n-1) + dp(n-2);
    }
}

但是这种解法是通过递归所有可能的情况来得出最终解,时间复杂度较高。这是因为存在大量“重叠子问题”。

比如想要计算 dp(6) ,就要计算dp(5) 和 dp(4)。计算dp(5) 又要计算dp(4) 和 dp(3)。但是dp(4) 已经在计算dp(6) 中计算过了。如果我们用一个备忘录把计算过的结果记录下来,需要的时候直接提取而不是重新计算,那么时间复杂度就会降下来。

使用了备忘录自上而下的递归解法

class Solution {
    public int climbStairs(int n) {
        //定义一个备忘录数组用于记录数据
        int[] memo = new int[n+1];
        return dp(n,memo);
    }
    
    //定义一个方法dp,表示:有多少种方法爬上n阶阶梯
    public int dp(int n,int[] memo){
        //先将备忘录初始化为 -1
        Arrays.fill(memo,-1);
        //base case
        if(n == 1 || n == 0){
            return 1;
        }
        if(memo[n] != -1){
            return memo[n];
        }
        //写出状态转移方程,并更新备忘录
        memo[n] = dp(n-1,memo) + dp(n-2,memo);
        return memo[n];
    }
}

在这种方法中我们使用了一个数组 memo 来记录结果。例如 memo[5] 表示爬上5阶台阶的爬法,对应 dp(5) 的结果。

但是这里有两个问题需要注意:memo数组的长度应该是多少?memo 数组的初始化应该是多少?

问题1: memo数组的长度应该是多少?

我们设定memo数组的长度为 n+1,是为了让memo[n] 对应 dp[n] 的结果,如果memo数组的长度为n的话,memo[n-1] 对应dp(n),dp(0)就只能对应 memo[-1]了。

数组的索引怎么能是 -1 呢?

问题2: memo 数组的初始化应该是多少?

初始化值应该设定为一个 dp 函数无法取到的值。

如果把memo数组中的元素初始化为1。dp(1) = 1,memo[1] = 1。那么我怎么直到这个返回值是dp数组的返回值,还是memo数组初始化的返回值呢?

所以说,初始化值应该设定为一个 dp 函数无法取到的值。

使用了dp数组的自下而上的迭代解法

我们定义一个dp数组,数组元素即为结果。比如dp[n] 定义为 : 爬上n阶阶梯的方法

class Solution {
    public int climbStairs(int n) {
        /*dp[i] 表示的是登录 i层阶梯的方法数量*/
        int[] dp = new int[n+1];
        if(n <= 2){
            return n;
        }
        //base case
        dp[0] = 0;dp[1] = 1;dp[2] = 2;
        for(int i = 3;i <= n;i++){
            dp[i] = dp[i-1] + dp[i-2];
        }
        return dp[n];
    }
}

问题1:迭代解法和递归解法的区别

两者的定义起始并无区别。

dp(n) 的定义为 “爬上n阶阶梯的爬法”,dp[n]也是一样的。

dp(n) 是递归解法。比如想要算出dp(4),要算出dp(3) 和dp(2),依次往下,层层递进。直到递进到base case后,拿到数据,在层层回归,得到dp(4)。

而dp[n] 的迭代解法是自下而上的。逐步算出dp[1]、dp[2]、dp[3].....直到算出dp[n]。


那么对于其他题也是如此吗?我们来试试用这个方法算下一道题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/320147.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

FineBI实战项目一(21):不同支付方式订单总额分析开发

点击新建组件&#xff0c;创建不同支付方式订单总额组件。 选择饼图&#xff0c;拖拽total_money到角度&#xff0c;拖拽pay_type到颜色&#xff0c;调节内径。 修改颜色的标识文字。 将组件拖拽到仪表板。 结果如下&#xff1a;

用C语言实现哈希表HashMap

代码仓库地址 1. 功能说明 自定义初始容量和负载因子&#xff1b;当键值对的个数与容量比值超过负载因子时&#xff0c;自动扩容&#xff1b;借鉴Java8的HashMap部分扩容逻辑&#xff0c;定义了单独的桶结构体用于记录hash值&#xff0c;以及2倍扩容&#xff0c;减少了hash运算…

K8S--service

一、简介 Service 是将集群中的 一个或一组 Pod应用程序公开为网络服务的方法。我们都知道pod是不稳定的,有可能时时刻刻都在创建和销毁,这一时刻运行的 Pod 集合可能不同于下一刻运行该应用的 Pod 集合,并且新创建的pod的ip地址会改变,所以我们不应该寄期望于pod的稳定性…

Calibre DESIGNrev Object Selection Toolbar

包括 Reference Path Polygon Edge Vertex Text的解释说明 FieldDescription用法&#xff08;勾选后&#xff09;ReferenceUsed to move or select a cell reference or array reference.可以选择一个cellPathUsed to move or select a contiguous path object.暂时不明请指教…

浏览器进程模型和JS的事件循环

一、浏览器的进程模型 1、什么是进程&#xff1f; 程序运行所需要的专属内存空间 2、什么是线程&#xff1f; ​​​​​运行​代码的称为线程&#xff08;同一个进程中的线程共享进程的资源&#xff09; ⼀个进程⾄少有⼀个线程&#xff0c;所以在进程开启后会⾃动创建⼀个线…

k8s云原生环境搭建笔记——第二篇

目录 1、使用普通方式安装prometheus和grafana1.1、安装kube-state-metrics容器1.1.1、下载并修改yaml文件1.1.2、导入kube-state-metrics镜像1.1.3、执行yaml文件目录 1.2、安装node-exploer1.2.1、创建名称空间prometheus1.2.2、执行yaml 1.3、安装prometheus1.3.1、创建集群…

【JUC进阶】14. TransmittableThreadLocal

目录 1、前言 2、TransmittableThreadLocal 2.1、使用场景 2.2、基本使用 3、实现原理 4、小结 1、前言 书接上回《【JUC进阶】13. InheritableThreadLocal》&#xff0c;提到了InheritableThreadLocal虽然能进行父子线程的值传递&#xff0c;但是如果在线程池中&#x…

e2studio开发三轴加速度计LIS2DW12(4)----测量倾斜度

e2studio开发三轴加速度计LIS2DW12.4--测量倾斜度 概述视频教学样品申请源码下载计算倾斜角度工作原理单轴倾斜检测双轴倾斜检测三轴倾斜检测通信模式管脚定义IIC通信模式速率新建工程工程模板保存工程路径芯片配置工程模板选择时钟设置UART配置UART属性配置设置e2studio堆栈e…

自编C++题目——输入程序

预估难度 简单 题目描述 小明编了一个输入程序&#xff0c;当用户的输入之中有<时&#xff0c;光标移动到最右边&#xff1b;当输入有>时&#xff0c;光标移动到最左边&#xff0c;当输入有^时&#xff0c;光标移动到前一个字符&#xff0c;当输入为#时&#xff0c;清…

大模型实战营Day4 XTuner 大模型单卡低成本微调实战 作业

按照文档操作&#xff1a; 单卡跑完训练&#xff1a; 按照要求更改微调的数据&#xff1a; 完成微调数据的脚本生成&#xff1a; 修改配置文件&#xff1a; 替换好文件后启动&#xff1a; 启动后终端如图&#xff1a; 用于微调的一些数据显示&#xff1a; 训练时间&#x…

记录一次git merge后发现有些文件不对的问题,排查过程

分支进行merge&#xff08;A merge到B&#xff09;之后&#xff0c;发现string.xml中有些字段的值没有merge过来&#xff0c;一开始还以为自己是自己merge错误&#xff0c;检查了一遍自己的merge操作没有问题。 那为啥没有merge过来呢&#xff1f;有一种可能是&#xff0c;merg…

vue2实现日历12个月平铺,显示工作日休息日

参考&#xff1a;https://blog.csdn.net/weixin_40292154/article/details/125312368 1.组件DateCalendar.vue&#xff0c;sass改为less <template><div class"cc-calendar"><div class"calendar-title"><span>{{ year }}年{{ mo…

【Linux Shell】5. 运算符

文章目录 【 1. 算术运算符 】1.1 expr 命令1.2 [ ] 方括号 【 2. 关系运算符 】【 3. 布尔运算符 】【 4. 逻辑运算符 】【 5. 字符串运算符 】【 6. 文件测试运算符 】 【 1. 算术运算符 】 运算符说明举例赋值a$b 把变量 b 的值赋给 a。 1.1 expr 命令 原生 bash 不支持简…

SDRAM小项目——写模块

写模块跟着视频看了一个多星期&#xff0c;一开始始终有点弄不清楚&#xff0c;现在记录一下理解的过程。 阅读文档信息&#xff1a; 首先阅读文档信息&#xff0c;了解SDRAM写过程的状态转换和时序图 SDRAM整体状态流程如图所示&#xff1a; 在SDRAM整体系统中&#xff0c…

数据结构之bool类

bool类 bool 是布尔类。它是最简单的一个类&#xff0c;其取值有两种&#xff0c;1和O&#xff0c;即 True 和 False。可以这样简单地理解&#xff0c;除了1和0以及 True 和 False 的情况之外&#xff0c;但凡有值&#xff08;非空&#xff09;即为真&#xff0c;但凡无值&…

nodemon(自动重启 node 应用程序)的安装与使用

1、安装&#xff0c;在随意一个命令窗口都可以 我们可以执行安装选项 -g 进行全局安装 npm i -g nodemon 全局安装完成之后就可以在命令行的任何位置运行 nodemon 命令 该命令的作用是 自动重启 node 应用程序 2、使用&#xff1a; 可能报错如下 windows 默认不允许 npm …

【数据结构 | 直接插入排序】

直接插入排序 基本思路直接插入排序SelectSort 基本思路 扑克牌是我们几乎每个人都可能玩过的游戏最基本的扑克玩法都是—边模牌,— 边理牌。加入我们拿到如图这样的扑克牌&#xff1a; 我们会按照如下理牌&#xff1a; 将3和4移动至5的左侧&#xff0c;再将2移动到最左侧&a…

顺序表的实现(上)(C语言)

本文章主要对顺序表的介绍以及数据结构的定义,以及几道相关例题,帮助大家更好理解顺序表. 文章目录 前言 一、顺序表的静态实现 二、顺序表的动态实现 三.定义打印顺序表函数 四.定义动态增加顺序表长度函数 五.创建顺序表并初始化 六.顺序表的按位查找 七.顺序表的按值…

网络爬虫丨基于scrapy+mysql爬取博客信息并保存到数据库中

文章目录 写在前面实验描述实验框架实验需求 实验内容1.安装依赖库2.创建Scrapy项目3.配置系统设置4.配置管道文件5.连接数据库6.分析要爬取的内容7.编写爬虫文件 运行结果写在后面 写在前面 本期内容&#xff1a;基于scrapymysql爬取博客信息并保存到数据库中 实验需求 ana…

java并发编程

一、java线程 1.三种创建线程的方式 Integer sum futureTask.get(); 会等待其对应的线程执行完 &#xff0c;即阻塞 再获得结果。 所以我在测试时&#xff0c;出现一个小插曲 Slf4j public class ThreeWays {//1.自定义MyThread进行继承Threadstatic void test001(){Thread t…