代码随想录day32--动态规划理论基础

news2024/12/23 5:59:50

什么是动态规划

动态规划简称DP,如果某一问题有很多重叠子问题,使用动态规划是最有效的。

所以动态规划中每一个状态一定是由上一个状态推导出来的,这一点一定要和贪心区别出来,贪心没有状态推导,而是直接从局部直接选择最优。

在贪心中,有一个例子是背包问题。

eg:由N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i],得到的价值是value[i]。每件物品只能使用一次,求解将哪些物品装进背包里物品价值总和最大。

动态规划中dp[j]是由dp[j-weight]推导出的,然后取max(dp[j],dp[j-weight[i]+value[i])。

但如果是使用贪心,每次拿物品只会选择一个最大的或者最小的,和之前的状态没有什么关系。所以贪心解决不了动态规划的问题。

大家只需要明白动态规划是由前一个状态推导出来的,而贪心是局部直接选出最优的。


动态规划的解题步骤

做动态规划的题目的时候,很多同学包括我自己都有一个误区,就算将状态转移公式背下,然后照葫芦画瓢,然后根据公式进行代码书写,无论题目是否通过,都不清楚我们使用的dp[i]到底有什么用。

状态转移公式(递推公式)是很重要,但是仔细了解过后,或发现,动态规划不仅仅由递推公式。

对于动态规划,将其拆分为以下五部曲,这五步都搞懂,就基本可以把动态规划掌握:

1.确定dp数组以及其小标的含义

2.确定递推公式

3.dp数组如何初始化

4.确定遍历顺序

5.举例推导dp数组

*一定不要忽略初始化,因为一些情况是递推公式决定了dp数组要如何初始化


动态规划应该如何debug

绝大部分同学,对于动态规划的题目是,看题解感觉会了,然后照葫芦画瓢,如果能过那就没关系,但是如果没过,那么怎么改都过不去,对于之前说的dp数组的初始化,递推公式,遍历顺序,都是处于一种什么都不明白的的状态。

找到问题的最好办法就是将dp数组打印出来,看看究竟是不是按照自己的思路推导的

做动态规划的题目,写代码之前一定要把状态转移在dp数组的上具体情况模拟一遍,心中有数,最后确定推出的是否是想要的结果。

然后再写代码,如果代码没有通过,那就打印dp数组,看看是不是自己预先推导的哪里有出入。

如果打印出和自己预先模拟推导的一样,那么就算自己的递归公式、初始化或者遍历顺序有问题。如何还是不一样,那就是代码实现的细节有问题了。

这才是一个完整的思考过程,而不是代码出现了问题,就毫无头绪的乱改,最后依旧过不了,或者是稀里糊涂的过

从现在开始,写动态规划的题目出现问题或者无法通过,自己可以先思考这三个问题:

1.这道题我距离推导状态公式了吗

2.我们打印dp数组了吗

3.打印出的dp数组和我想的一样吗

如果这三个问题实现了,那么基本上动态规划的题目也就是解决了

亦或者是可以更清晰的明白自己究竟是哪一点不明白,是状态转移不明白,还是具体代码不知道该怎么写,还是不理解遍历dp数组的顺序。这样带着目的性的问题,就很好了。


LeetCode509.斐波那契数

题目描述:

斐波那契数 (通常用 F(n) 表示)形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是:

F(0) = 0,F(1) = 1
F(n) = F(n - 1) + F(n - 2),其中 n > 1

给定 n ,请计算 F(n) 。

示例 1:

输入:n = 2
输出:1
解释:F(2) = F(1) + F(0) = 1 + 0 = 1

示例 2:

输入:n = 3
输出:2
解释:F(3) = F(2) + F(1) = 1 + 1 = 2

示例 3:

输入:n = 4
输出:3
解释:F(4) = F(3) + F(2) = 2 + 1 = 3

解题思路:

·这道题大家再熟悉不过了,但是就是因为这题比较简单,所以大家可能并没有做什么分析,就顺手写过了,我们使用动态规划五部曲进行分析

1.确定dp数组以及下标的含义

dp[i]的定义为:第i个数的斐波那契数值是dp[i]

2.确定递推公式

因为题目已经把递推公式给我们了,所以可以直接使用 dp[i] = dp[i-1] + dp[i-2]

3.dp数组如何初始化

题目中给的也很明确了

dp[0] = 0;dp[1] = 1;

4.确定遍历顺序,从递归公式中我们可以看出dp[i]是依赖dp[i-1]和dp[i-2],那么遍历顺序,就算一定是从前到后遍历

5.举例推导dp数组

按照递推公式,我们推导一下,当n为10时,数列应该是:0 1 1 2 3 5 8 13 21 34 55

如果最终代码无法通过,我们就将dp数组打印出,观察与我们推导的数列是不是一致的

以上,使用动态规划的方法全部分析完毕,代码如下:

class Solution {
public:
    int fib(int n) {
        if(n <= 1) return n;
        vector<int> dp(n+1);
        dp[0] = 0;
        dp[1] = 1;
        for(int i = 2;i <= n;i++){
            dp[i] = dp[i-1]+dp[i-2];
        }
        return dp[n];
    }
};

·时间复杂度:O(n)

·空间复杂度:O(n)

总结:这道题虽然说非常的基础,但是我们严格按照动态规划五部曲进行分析,发现其实分析过程也并不会复杂,我们使用简单题目用于掌握方法,接下来的方法会越来越重要。

LeetCode70.爬楼梯

题目描述:

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

示例 1:

输入:n = 2
输出:2
解释:有两种方法可以爬到楼顶。
1. 1 阶 + 1 阶
2. 2 阶

示例 2:

输入:n = 3
输出:3
解释:有三种方法可以爬到楼顶。
1. 1 阶 + 1 阶 + 1 阶
2. 1 阶 + 2 阶
3. 2 阶 + 1 阶

解题思路:

·这题仔细分析,多举几个例子,就会发现其规律。爬一层楼梯有一种方法,爬两层楼梯有两种方法,爬三层楼梯就可以根据前两层的规律进行推导

我们来进行动态规划五部曲的分析:

1.确定dp数组以及下标的含义

dp[i]:爬到第i层楼梯,有dp[i]种方法

2.确定递推公式

这道题怎么样才能推导出dp[i]呢?

从dp[i]的定义可以看出,dp[i]可以有两个方向可以推导出

首先是dp[i-1],上i-1层楼梯,有dp[i-1]种方法,那么再一步跳一个台阶不就是dp[i]了

dp[i-2]也是一样的思想,有dp[i-2]种方法,那么再一步跳两个个台阶不就是dp[i]了

所以就可以得出结论 dp[i]就算dp[i-1]和dp[i-2]之和

在推导dp[i]的时候,一定要时刻想着dp[i]的定义,这就体现出确定dp数组以及下标的含义的重要性

3.dp数组如何初始化

dp[0] = 1,因为不动也是一种移动方法

dp[1] = 1,只有一步可以走

dp[2]  = 2 ,有一步和两步可以走,所以我们就可以从i=3开始递推

4.确定遍历顺序

从递推公式dp[i] = dp[i-1]+dp[i-2]可以看出,遍历顺序一定是从前向后的

5.举例推导dp数组

我们会发现这个数组推导出来就算斐波那契,如果代码出现问题,那么就把dp数组打印出来,看看是否和自己推导的一样

代码如下:

class Solution {
public:
    int climbStairs(int n) {
        if(n <= 1) return n;
        vector<int> dp(n+1);
        dp[0] = 1;
        dp[1] = 1;
        for(int i = 2;i <= n;i++){
            dp[i] = dp[i-1]+dp[i-2];
        }
        return dp[n];
    }
};

 时间复杂度:O(n)

空间复杂度:O(n) 

总结:这道题其实和斐波那契数题目是一样的,但是动态规划五部曲,却比上一题复杂了那么多

关键就算斐波那契题目描述中,已经把递归公式和如何初始化都给出来了,剩下的就很好推出

但是本题需要逐个分析,大家应该初步感受出了动态规划的五部曲了

LeetCode746.使用最小花费爬楼梯

题目描述:

给你一个整数数组 cost ,其中 cost[i] 是从楼梯第 i 个台阶向上爬需要支付的费用。一旦你支付此费用,即可选择向上爬一个或者两个台阶。

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

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

示例 1:

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

示例 2:

输入:cost = [1,100,1,1,1,100,1,1,100,1]
输出:6
解释:你将从下标为 0 的台阶开始。
- 支付 1 ,向上爬两个台阶,到达下标为 2 的台阶。
- 支付 1 ,向上爬两个台阶,到达下标为 4 的台阶。
- 支付 1 ,向上爬两个台阶,到达下标为 6 的台阶。
- 支付 1 ,向上爬一个台阶,到达下标为 7 的台阶。
- 支付 1 ,向上爬两个台阶,到达下标为 9 的台阶。
- 支付 1 ,向上爬一个台阶,到达楼梯顶部。
总花费为 6 。

解题思路:

1.确定dp数组以及下标的含义

使用动态规划,需要使用一个数组来记录状态,本题只需要一个一维数组dp[i]就可以了

dp[i]的定义:达到第i个台阶所花费的最少体力为dp[i]

2.对于递推公式

得到dp[i],需要知道dp[i-1]和dp[i-2]

而dp[i-1]跳到dp[i]需要花费dp[i-1]+cost[i-1]

dp[i-2]跳到dp[i]需要花费dp[i-2]+dp[i-2]

按照题目要求,所以我们选择最小的,dp[i] = min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2])

3.dp数组如何初始化

看了递推公式,所以我们就可以知道需要初始化dp[i-1]和dp[i-2],也就是dp[0]dp[1]

题目中说可以选择下标为0或下标为1的台阶开始爬楼梯,也就是说到达第0个台阶和第一个台阶是不花费的,所以dp[0] = dp[1] = 0

4.确定遍历顺序

因为是模拟台阶,而且dp[i]由dp[i-1]和dp[i-2]推出,所以是从前到后遍历cost数组即可\

5.举例推导dp数组

我们使用示例2进行举例

如果代码出现问题,就将dp数组打印出来,看看和如上推导是不是一样的

代码如下:

class Solution {
public:
    int minCostClimbingStairs(vector<int>& cost) {
        vector<int> dp(cost.size()+1);
        dp[0] = 0;
        dp[1] = 0;
        for(int i = 2;i <= cost.size();i++){
            dp[i] = min(dp[i-2]+cost[i-2],dp[i-1]+cost[i-1]);
        }
        return dp[cost.size()];
    }
};

·时间复杂度:O(n)

·空间复杂度:O(n)

总结:这题和爬楼梯也是类似,只是多了一个需要花费的部分,所以并没有什么难理解的地方

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

新版Java面试专题视频教程——虚拟机篇②

新版Java面试专题视频教程——虚拟机篇② 3 垃圾收回3.1 简述Java垃圾回收机制&#xff1f;&#xff08;GC是什么&#xff1f;为什么要GC&#xff09;3.2 对象什么时候可以被垃圾器回收3.2.1 引用计数法3.2.2 可达性分析算法 3.3 JVM 垃圾回收算法有哪些&#xff1f;——4种3.3…

消息中间件篇之RabbitMQ-高可用机制

一、怎么保证高可用性 在生产环境下&#xff0c;使用集群来保证高可用性&#xff0c;一般我们采用普通集群、镜像集群、仲裁队列。 二、普通集群 普通集群&#xff0c;或者叫标准集群&#xff08;classic cluster&#xff09;&#xff0c;具备下列特征&#xff1a; 1. 会在集…

<网络安全>《50 网络攻防专业课<第十四课 - 华为防火墙的使用(3)>

7防火墙的防范技术&#xff08;2&#xff09; 7.1 DNS Flood攻击防范 攻击介绍 攻击者在短时间内通过向DNS&#xff08;Domain Name System&#xff09;服务器发送大量的查询报文&#xff0c;使得服务器不得不对所有的查询请求进行回应&#xff0c;导致DNS服务器无法为合法用户…

hbuilderx创建、运行uni-app

创建uni-app 在点击工具栏里的文件 -> 新建 -> 项目&#xff1a; 选择uni-app类型&#xff0c;输入工程名&#xff0c;选择模板&#xff0c;点击创建&#xff0c;即可成功创建。 uni-app自带的模板有 Hello uni-app &#xff0c;是官方的组件和API示例。还有一个重要模…

K线实战分析系列之六:启明星——空方力量减弱信号

KK线实战分析系列之六&#xff1a;启明星——空方力量减弱信号 一、星线二、多种反转形态三、启明星形态四、启明星形态的总结 一、星线 星线在单根K线形态上是属于纺锤线&#xff0c;之所以被称为星线&#xff0c;主要是因为它在行情当中的相对位置&#xff0c;区别于其他纺锤…

HC595级联原理及实例 - STM32

74HC595的最重要的功能就是&#xff1a;串行输入&#xff0c;并行输出。其次&#xff0c;74HC595里面有2个8位寄存器&#xff1a;移位寄存器、存储寄存器。74HC595的数据来源只有一个口&#xff0c;一次只能输入一个位&#xff0c;那么连续输入8次&#xff0c;就可以积攒为一个…

H桥电流回路分析(单极性调制)

当Q7Q16导通时&#xff0c;是Q7高低电平切换&#xff0c;Q16一直导通 当Q7导通时&#xff0c;电感为左正右负&#xff08;电感起到阻碍电流变化的作用&#xff09;为红色线 当Q7关断的时候&#xff0c;电感&#xff08;作为源&#xff09;为左负右正&#xff0c;此时电流回路…

Stable Diffusion 3 发布及其重大改进

1. 引言 就在 OpenAI 发布可以生成令人瞠目的视频的 Sora 和谷歌披露支持多达 150 万个Token上下文的 Gemini 1.5 的几天后&#xff0c;Stability AI 最近展示了 Stable Diffusion 3 的预览版。 闲话少说&#xff0c;我们快来看看吧&#xff01; 2. 什么是Stable Diffusion…

RHEL9安装Python2.7

RHEL9作为2022年5月新推出的版本&#xff0c;较RHEL8有了很多地方的改进&#xff0c;而且自带很多包&#xff0c;功能非常强大&#xff0c;稳定性和流畅度也较先前版本有了很大的提升。RHEL9自带python3.9&#xff0c;但是过高版本的python不可避免地会导致一些旧版本包地不兼容…

《Docker 简易速速上手小册》第1章 Docker 基础入门(2024 最新版)

文章目录 1.1 Docker 简介与历史1.1.1 Docker 基础知识1.1.2 重点案例&#xff1a;Python Web 应用的 Docker 化1.1.3 拓展案例 1&#xff1a;使用 Docker 进行 Python 数据分析1.1.4 拓展案例 2&#xff1a;Docker 中的 Python 机器学习环境 1.2 安装与配置 Docker1.2.1 重点基…

主机开机正常但是显示器不亮怎么办 电脑故障问题解答

随着科技的不断发展&#xff0c;电脑或许已经是我们日常生活中最常接触的设备之一了。但是设备毕竟是设备&#xff0c;用久了自然会出毛病&#xff0c;开机的时候&#xff0c;主机开了&#xff0c;但是电脑显示屏不亮&#xff0c;这时候我们该怎么处理呢?下面我就来介绍几种解…

ES坑-创建索引使用_下划线-黑马旅游搜不到

学ES的时候&#xff0c;星级过滤无效 找不到数据。 需要 但是我们在创建的时候使用的是keyword 通过研究发现&#xff0c;我们导入数据的时候应该默认的为starName 我get库时候发现有2个字段 所以通过star_name搜索因为都是空数据搜不到&#xff0c;而starName类型为text所以…

MFC由初值终值步长生成数值序列

matlab的冒号运算符可以生成数值序列; 下面来生成自己的数值序列; vc6新建一个对话框工程; 放几个控件;添加成员变量如下; void CMycolonDlg::OnButton1() {// TODO: Add your control notification handler code hereUpdateData(TRUE);double d1, d2;CString str1, …

Qt MDI应用方法:QMdiArea和QMdiSubWindows类

重点&#xff1a; 1.使用MDI应用程序&#xff0c;需要在主窗口的工作区放置一个QMdiArea组件。 并将QMdiArea组件设置成中心窗口 2.MDI有两个显示模式&#xff1a;Tab多页显示模式和子窗口显示模式 子窗口显示模式有两种显示方法&#xff1a;窗口级联展开和平铺展开 窗口级联…

[electron]官方示例解析

官方例子 github链接 main.js const { app, BrowserWindow } require(electron)说句实话这里的语法是有部分看不懂的。导入模块虽然electron有很多模块。但是这里只是用到了app 和 BrowserWindow function createWindow () {// Create the browser window.const mainWindo…

零基础学编程,编程简单学,中文编程工具下载及工具箱进度条构件的用法

一、前言 今天给大家分享的中文编程开发语言工具 进度条构件的用法。 编程入门视频教程链接 https://edu.csdn.net/course/detail/39036 编程工具及实例源码文件下载可以点击最下方官网卡片——软件下载——常用工具下载——编程工具免费版下载及实例源码下载。 进度条 进度…

Javase补充-Arrays类的常用方法汇总

文章目录 一 . 排序方法二 . 查找方法三 . 判断是否相等的方法四 . 拷贝方法五 . 填充方法 一 . 排序方法 我们第一个要介绍的就是sort方法 这个排序实现的底层逻辑应该是十分复杂的,以我们目前的水平体系应该无法理解,我们今天尝试用我们可以理解的一种排序算法,插入排序来模…

Nodejs+vue图书阅读评分个性化推荐系统

此系统设计主要采用的是nodejs语言来进行开发&#xff0c;采用 vue框架技术&#xff0c;对于各个模块设计制作有一定的安全性&#xff1b;数据库方面主要采用的是MySQL来进行开发&#xff0c;其特点是稳定性好&#xff0c;数据库存储容量大&#xff0c;处理能力快等优势&#x…

C#,动态规划(DP)模拟退火(Simulated Annealing)算法与源代码

1 模拟退火 *问题:**给定一个成本函数f:r^n–>r*&#xff0c;找到一个 n 元组&#xff0c;该元组最小化 f 的值。请注意&#xff0c;最小化函数值在算法上等同于最大化(因为我们可以将成本函数重新定义为 1-f)。 很多有微积分/分析背景的人可能都熟悉单变量函数的简单优化。…

Linux---进程间通信(下)

1、System V 共享内存 原理如下图 系统调用接口介绍 int shmget(key_t key, size_t size, int shmflg) 功能&#xff1a;用来创建共享内存 参数 key&#xff1a;这个共享内存段名字&#xff0c;内核用key来标识共享内存size&#xff1a;共享内存大小shmflg&#xff1a;由九个权…