【算法题】动态规划中级阶段之不同路径、最小路径和

news2024/11/26 2:29:07

动态规划中级阶段

  • 前言
  • 一、不同路径
    • 1.1、思路
    • 1.2、代码实现
  • 二、不同路径 II
    • 2.1、思路
    • 2.2、代码实现
  • 三、最小路径和
    • 3.1、思路
    • 3.3、代码实现
  • 总结

前言

动态规划(Dynamic Programming,简称 DP)是一种解决多阶段决策过程最优化问题的方法。它是一种将复杂问题分解成重叠子问题的策略,通过维护每个子问题的最优解来推导出问题的最优解。

动态规划的主要思想是利用已求解的子问题的最优解来推导出更大问题的最优解,从而避免了重复计算。因此,动态规划通常采用自底向上的方式进行求解,先求解出小规模的问题,然后逐步推导出更大规模的问题,直到求解出整个问题的最优解。

动态规划通常包括以下几个基本步骤:

  1. 定义状态:将问题划分为若干个子问题,并定义状态表示子问题的解;
  2. 定义状态转移方程:根据子问题之间的关系,设计状态转移方程,即如何从已知状态推导出未知状态的计算过程;
  3. 确定初始状态:定义最小的子问题的解;
  4. 自底向上求解:按照状态转移方程,计算出所有状态的最优解;
  5. 根据最优解构造问题的解。

动态规划可以解决许多实际问题,例如最短路径问题、背包问题、最长公共子序列问题、编辑距离问题等。同时,动态规划也是许多其他算法的核心思想,例如分治算法、贪心算法等。

动态规划是一种解决多阶段决策过程最优化问题的方法,它将复杂问题分解成重叠子问题,通过维护每个子问题的最优解来推导出问题的最优解。动态规划包括定义状态、设计状态转移方程、确定初始状态、自底向上求解和构造问题解等步骤。动态规划可以解决许多实际问题,也是其他算法的核心思想之一。

一、不同路径

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。

问总共有多少条不同的路径?

在这里插入图片描述

示例 1:

输入:m = 3, n = 7
输出:28

示例 2:

输入:m = 3, n = 2
输出:3
解释:
从左上角开始,总共有 3 条路径可以到达右下角。

  1. 向右 -> 向下 -> 向下
  2. 向下 -> 向下 -> 向右
  3. 向下 -> 向右 -> 向下

示例 3:

输入:m = 7, n = 3
输出:28

示例 4:

输入:m = 3, n = 3
输出:6

来源:力扣(LeetCode)。

1.1、思路

令 dp[i][j] 是到达 i, j 最多路径

动态方程:dp[i][j] = dp[i-1][j] + dp[i][j-1]

注意,对于第一行 dp[0][j],或者第一列 dp[i][0],由于都是在边界,所以只能为 1

时间复杂度:O(m∗n)

空间复杂度:O(m∗n)

优化:因为每次只需要 dp[i-1][j],dp[i][j-1],所以只要记录这两个数。

1.2、代码实现

class Solution {
public:
    int uniquePaths(int m, int n) {
        long long ans = 1;
        for (int x = n, y = 1; y < m; ++x, ++y) {
            ans = ans * x / y;
        }
        return ans;
    }
};

二、不同路径 II

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish”)。

现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?

网格中的障碍物和空位置分别用 1 和 0 来表示。

示例1:

输入:obstacleGrid = [[0,0,0],[0,1,0],[0,0,0]]
输出:2
解释:3x3 网格的正中间有一个障碍物。
从左上角到右下角一共有 2 条不同的路径:

  1. 向右 -> 向右 -> 向下 -> 向下
  2. 向下 -> 向下 -> 向右 -> 向右
    在这里插入图片描述

示例2:

输入:obstacleGrid = [[0,1],[0,0]]
输出:1
在这里插入图片描述

2.1、思路

递归思路:
假设我们定义到达右下角的走法数为 f(m,n), 因为右下角只能由它上方或者左方的格子走过去,因此可以很容易的写出递归求解式,即 f(m,n)=f(m−1,n)+f(m,n−1),最后加上递归终止条件大功告成啦!

然而事情并木有结束~ 因为这样自底向上的递归会存在大量的重复计算,所以将其改写为在二维数组中自顶向下的递推即可,即 dp[i,j]=dp[i−1,j]+dp[i,j−1]。

1、状态定义:dp[i][j] 表示走到格子 (i,j) 的方法数。

2、状态转移:(i,j) 上有障碍物,则 dp[i][j] 值为 0,表示走到该格子的方法数为 0;否则网格 (i,j) 可以从网格 (i−1,j) 或者 网格 (i,j−1) 走过来,因此走到该格子的方法数为走到网格 (i−1,j) 和网格 (i,j−1) 的方法数之和,即 dp[i,j]=dp[i−1,j]+dp[i,j−1]。
状态转移方程如下:
在这里插入图片描述
3、初始条件:

  • 第 1 列的格子只有从其上边格子走过去这一种走法,因此初始化 dp[i][0] 值为 1,存在障碍物时为 0;
  • 第 1 行的格子只有从其左边格子走过去这一种走法,因此初始化 dp[0][j] 值为 1,存在障碍物时为 0。

2.2、代码实现

class Solution {
public:
    int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
        int n = obstacleGrid.size(), m = obstacleGrid.at(0).size();
        vector <int> f(m);

        f[0] = (obstacleGrid[0][0] == 0);
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < m; ++j) {
                if (obstacleGrid[i][j] == 1) {
                    f[j] = 0;
                    continue;
                }
                if (j - 1 >= 0 && obstacleGrid[i][j - 1] == 0) {
                    f[j] += f[j - 1];
                }
            }
        }

        return f.back();
    }
};

时间复杂度:O(nm),其中 n 为网格的行数,m 为网格的列数。我们只需要遍历所有网格一次即可。

空间复杂度:O(m)。利用滚动数组优化,我们可以只用 O(m) 大小的空间来记录当前行的 f 值。

三、最小路径和

给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。

说明:每次只能向下或者向右移动一步。

示例 1:

输入:grid = [[1,3,1],[1,5,1],[4,2,1]]
输出:7
解释:因为路径 1→3→1→1→1 的总和最小。
在这里插入图片描述

示例 2:

输入:grid = [[1,2,3],[4,5,6]]
输出:12

来源:力扣(LeetCode)。

3.1、思路

状态定义:设 dp 为大小 m×n 矩阵,其中 dp[i][j] 的值代表直到走到 (i,j) 的最小路径和。

由于路径的方向只能是向下或向右,因此网格的第一行的每个元素只能从左上角元素开始向右移动到达,网格的第一列的每个元素只能从左上角元素开始向下移动到达,此时的路径是唯一的,因此每个元素对应的最小路径和即为对应的路径上的数字总和。

对于不在第一行和第一列的元素,可以从其上方相邻元素向下移动一步到达,或者从其左方相邻元素向右移动一步到达,元素对应的最小路径和等于其上方相邻元素与其左方相邻元素两者对应的最小路径和中的最小值加上当前元素的值。由于每个元素对应的最小路径和与其相邻元素对应的最小路径和有关,因此可以使用动态规划求解。

创建二维数组 dp,与原始网格的大小相同,dpdp[i][j] 表示从左上角出发到 (i,j) 位置的最小路径和。显然,dp[0][0]=grid[0][0]。对于 dp 中的其余元素,通过以下状态转移方程计算元素值。

当 i>0 且 j=0 时,dp[i][0]=dp[i−1][0]+grid[i][0]。

当 i=0 且 j>0 时,dp[0][j]=dp[0][j−1]+grid[0][j]。

当 i>0 且 j>0 时,dp[i][j]=min(dp[i−1][j],dp[i][j−1])+grid[i][j]。

最后得到 dp[m−1][n−1] 的值即为从网格左上角到网格右下角的最小路径和。

3.3、代码实现

class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        if (grid.size() == 0 || grid[0].size() == 0) {
            return 0;
        }
        int rows = grid.size(), columns = grid[0].size();
        auto dp = vector < vector <int> > (rows, vector <int> (columns));
        dp[0][0] = grid[0][0];
        for (int i = 1; i < rows; i++) {
            dp[i][0] = dp[i - 1][0] + grid[i][0];
        }
        for (int j = 1; j < columns; j++) {
            dp[0][j] = dp[0][j - 1] + grid[0][j];
        }
        for (int i = 1; i < rows; i++) {
            for (int j = 1; j < columns; j++) {
                dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];
            }
        }
        return dp[rows - 1][columns - 1];
    }
};

时间复杂度:O(mn)。

空间复杂度:O(mn)。

总结

动态规划(Dynamic Programming)是一种解决多阶段决策最优化问题的方法,它将复杂问题分解成重叠子问题并通过维护每个子问题的最优解来推导出问题的最优解。动态规划可以解决许多实际问题,例如最短路径问题、背包问题、最长公共子序列问题、编辑距离问题等。

动态规划的基本思想是利用已求解的子问题的最优解来推导出更大问题的最优解,从而避免了重复计算。它通常采用自底向上的方式进行求解,先求解出小规模的问题,然后逐步推导出更大规模的问题,直到求解出整个问题的最优解。

动态规划通常包括以下几个基本步骤:

  1. 定义状态:将问题划分为若干个子问题,并定义状态表示子问题的解;
  2. 定义状态转移方程:根据子问题之间的关系,设计状态转移方程,即如何从已知状态推导出未知状态的计算过程;
  3. 确定初始状态:定义最小的子问题的解;
  4. 自底向上求解:按照状态转移方程,计算出所有状态的最优解;
  5. 根据最优解构造问题的解。

动态规划的时间复杂度通常为 O ( n 2 ) O(n^2) O(n2) O ( n 3 ) O(n^3) O(n3),空间复杂度为O(n),其中n表示问题规模。在实际应用中,为了减少空间复杂度,通常可以使用滚动数组等技巧来优化动态规划算法。

在这里插入图片描述

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

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

相关文章

卸载及安装docker的教程-ubuntu

一、前言 万地高楼平地起~ 二、环境 OS&#xff1a;Ubuntu 20.04 64 bit 显卡&#xff1a;NVidia GTX 2080 Ti CUDA&#xff1a;11.2 三、卸载docker 1、删除docker及安装时自动安装的所有包 apt-get autoremove docker docker-ce docker-engine docker-ce-*for pkg in …

linux -信号量semphore分析

linux -信号量分析 1 struct semaphore和sema_init1.1 struct semaphore1.2 sema_init 2 down3 up4 down_interruptible5 down_killable6 down_timeout7 down_trylock 基于linux-5.15分析&#xff0c;信号量在使用是是基于spin lock封装实现的。 1 struct semaphore和sema_ini…

爬虫入门指南:如何使用正则表达式进行数据提取和处理

文章目录 正则表达式正则表达式中常用的元字符和特殊序列案例 使用正则表达式提取数据案例存储数据到文件或数据库使用SQLite数据库存储数据的示例代码SQLite基本语法创建表格&#xff1a;插入数据&#xff1a;查询数据&#xff1a;更新数据&#xff1a;删除数据&#xff1a;条…

【雕爷学编程】Arduino动手做(137)---MT8870语音解码

37款传感器与执行器的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止这37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&am…

【uview calendar日历 】如何选择今天之前的数据

在日常工作中&#xff0c;使用uniappuview的ui组件&#xff0c;使用日历组件默认是无法选择当前之前的日期&#xff0c;现在讲下解决的方法 设置 最小的可选日期minDate&#xff0c;最大可选日期maxDate&#xff0c; 默认选中的日期&#xff0c;mode为multiple或range是必须为数…

自定义选项卡组件,选项可插槽html

文件夹xxtabs 四个文件 index暴露 render vue添加虚拟节点到插槽&#xff08;自定义标签结构&#xff09; tabs选项卡整体 abpaneq切换区 tabs.vue <template><div class"gnip-tab"><div class"gnip-tab-nav"><divv-for"(item,…

“sudo”组不存在”或“用户不在 sudoers 文件中。此事将被报告”

解决方法: 使用命令&#xff1a;usermod -a -G sudo tom (换成其他的用户名&#xff0c;也是一个道理)&#xff0c;不过还是不行。 实际解决还是要执行 sudo visudo &#xff0c;在这个文件中去添加用户 这样修改之后&#xff0c;保存并退出&#xff0c;亲测有效&#xff01; …

【FFmpeg实战】AAC编码介绍

AAC&#xff08;Advanced Audio Coding&#xff0c;译为&#xff1a;高级音频编码&#xff09;&#xff0c;是由Fraunhofer IIS、杜比实验室、AT&T、Sony、Nokia等公司共同开发的有损音频编码和文件格式。 对比MP3 AAC被设计为MP3格式的后继产品&#xff0c;通常在相同的比…

训练自己的ChatGPT 语言模型(一).md

0x00 Background 为什么研究这个&#xff1f; ChatGPT在国内外都受到了广泛关注&#xff0c;很多高校、研究机构和企业都计划推出类似的模型。然而&#xff0c;ChatGPT并没有开源&#xff0c;且复现难度非常大&#xff0c;即使到现在&#xff0c;没有任何单位或企业能够完全复…

Atlas200 DK A2与Arduino进行UART串口通信

我们在做一些人工智能的应用开发时往往使用人工智能开发板作为上位机&#xff08;比如我们的小滕&#xff09;&#xff0c;Arduino、stm32等作为下位机控制板&#xff0c;通过上位机进行人工智能模型的推理之后进而给下位机传输对应的控制命令实现智能控制。那么如何实现两者的…

简化交互体验——探索Gradio的ClearButton模块

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️ &#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

ndarray对象怎样创建?ndarray基本属性列举

numpy中包含一个N维数组对象&#xff0c;即ndarray对象&#xff0c;该对象具有矢量算术能力和复杂的广播能力&#xff0c;常用于科学计算。ndarray对象中的元素可以通过索引访问&#xff0c;索引序号从0开始;ndarray对象中存储的所有元素的类型必须相同。创建ndarray对象的方式…

输入框设置placeholder的文字居中

<input classlogin-form-pwd placeholder请输入商家登录密码 placeholder-class"center"></input> .center{ text-align: center; }

Linux下 MHA故障切换 主从角色提升

目录 所有主机共同操作 manger操作 其他四台安装 搭建主从复制环境 nobe slave1 配置 slave2 slave3 配置 配置MHA环境 简述MHA&#xff1a; MHA&#xff08;Master High Availability&#xff09;目前在MySQL高可用方面是一个相对成熟的解决方案&#xff0c;它由…

MySQL中的DDL操作,MySQL中DML操作,MySQL查询数据,SQL函数,MySQL中的索引,MySQL事务,MySQL的用户管理,MySQL分页查询

目录 MySQL中的DDL操作 一、创建表与删除表 1&#xff0c;创建表 2&#xff0c;查看已创建的表。 3&#xff0c;删除表 二、修改表 1&#xff0c;修改表名 2&#xff0c;修改列名 3&#xff0c;修改列类型 4&#xff0c;添加新列 5&#xff0c;删除指定列 三、MySQ…

劳保鞋厂家带你深入了解防静电安全鞋——百华劳保

静电小则电人&#xff0c;大则引起火灾。静电的能量虽然不大&#xff0c;但其易放电&#xff0c;会出现静电火花&#xff0c;在易燃易爆场所&#xff0c;可能因静电火花引起火灾和爆炸。现在的工厂不少都是要求穿着具有防静电性能的工作鞋&#xff0c;很多劳动者都会穿着防静电…

使用Docker Swarm部署RabbitMQ+HAProxy高可用集群(三节点-镜像模式)

1. 部署规划 当前规划中&#xff0c;只启动一个HAProxy服务&#xff0c;主要用来做RabbitMQ节点的负载均衡和代理&#xff0c;但是HAProxy可能会出现单点故障&#xff0c;后续需要启动多个HAProxy节点&#xff0c;然后结合Keepalived来进行 设置虚拟IP 做故障转移 节点名称节…

nvm安装nodejs-2023年6月29日

nvm安装nodejs-2023年6月29日 cmd命令行&#xff0c;执行如下代码&#xff0c;表示安装最新稳定版本的node,这里默认是国外的node节点服务器 nvm install lts报错的话&#xff0c;找到安装目录&#xff0c;打开settings.txt&#xff0c;添加如下代码 更换node的国内淘宝镜像节…

cnocr安装和识别文字

cnocr 介绍 参考&#xff1a; cnocr: 极简的中文OCR Python包 https://cnocr.readthedocs.io/zh/latest/ cnocr 主要针对的是排版简单的印刷体文字图片&#xff0c;如截图图片&#xff0c;扫描件等。目前内置的文字检测和分行模块无法处理复杂的文字排版定位。如果要用于场景…

《从零开始编写一个直播服务器》音视频封装FLV

流媒体服务系列文章 文章目录 流媒体服务系列文章前言一、FLV 封装格式解析二、实例分析总结 前言 一、FLV 封装格式解析 flv header flv body flv header previous size0 tag1 previous size1 tag2 … prvious sizen tagn1 flv header previous size0 tag1 header ta…