算法学习笔记(8.2)-动态规划入门进阶

目录

问题判断:

问题求解步骤:

图例:

解析:

方法一:暴力搜索

实现代码如下所示:

解析:

方法二:记忆化搜索

代码示例:

解析:

方法三:动态规划

空间优化

代码如下

问题判断:

总的来说,如果一个问题包含重叠子问题、最优子结构,并满足无后效性,那么它通常适合用动态规划求解。然而,我们很难从问题的描述中直接提取出这些特征,因此通常需要放宽条件,首先需要观察问题适不适合使用回溯(穷举)解决。

适合用回溯解决的问题通常满足“决策树模型”,对于问题可以使用树形结构来描述,其中每一个节点代表一个决策,每一条路径代表一个决策序列。

换句话说,如果问题包含明确的决策概念,并且解是通过一系列决策产生的,那么它就满足决策树模型,通常可以使用回溯解决。

在此基础上,动态规划问题还有一些判断的“加分项”。

1. 问题包含最大(小)或最多(少)等优化描述。

2. 问题的状态能够使用一个列表、多维矩阵或树来表示,并且一个状态与其周围的状态存在递推关系。

相应地,也存在一些“减分项”。

1. 问题的目标是找出所有的解决方案,而不是找出最优解。

2. 问题描述中有明显的排列组合特征,需要返回具体的对个方案。

如果一个问题满足决策树模型,并具有较为明显的“加分项”,我们就可以假设它是一个动态规划问题,并在求解过程中验证它。

问题求解步骤:

动态规划的问题解题流程会因为问题的性质和难度有所不同,但通常遵循一下步骤:描述决策,定义状态,建立dp表,推导状态转移方程,确定边界问题等。

为了理解动态规划的解题的过程,使用一个经典的例题“最短路径和”

Question

给定一个n * m的二维网格grid,网格中的每个单元包含一个非负整数,表示该单元格的代价。机器人以左上角单元格为起始点,每次只能向下或者向右移动一步,直至到达右下角单元格。请返回左上角到右下角的最小路径和。

图例:

给定网格的最小路径和为13

第一步:思考每轮的决策,定义状态,从而得到dp表

本题的每一轮的决策就是从当前格子向下或者向右走一步。设当前格的行列索引为[i,j],则向下或向右走一步后,索引变为[i+1,j]或[i,j+1]。因此,状态应包含行索引和列索引两个变量,记为[i,j]。

状态[i,j]对应的子问题为:从起始点[0,0]走到[i,j]的最小路径和,记作dp[i,j]。

如图所示:dp二维矩阵,其尺寸与输入网格grid相同。

注意点:(Note)

动态规划和回溯过程可以描述成为一个决策序列,而状态由所有决策变量构成。应当包含描述解题进度的所有变量,其包含了足够的信息,能用来推导下一个状态。

每个状态都对应一个子问题,我们会定义成为一个dp表来存储所有子问题的解,状态的每个独立变量都是dp表中的一个维度。从本质上看,dp表是状态和子问题的解之间的映射。

第二步:找出最优子结构,进而推导出状态转移方程

对应状态[i,j],它只能从上边的格子[i-1,j]和左边的格子[I,j-1]转移过来。因此最优子结构为:到达[i,j]的最小路径和由[i-1,j]的最小路径和与[I,j-1的最小路径和哪个较小来决定。

根据以上的分析得出状态转移方程为:

dp[i,j] = min(dp[i-1,j],dp[i,j-1]) +grid[i,j]

图示如下:

注意点:(Note)

根据定义好的dp表,思考原问题和子问题的关系,找出通过子问题的最优解来构造原问题的最优解的方法,即最优子结构。

一旦我们找到了最优子结构,就可以使用它来构建出来状态转移方程。

第三步:确定边界条件和状态转移顺序

在本题中,处在首行的状态只能从其左边的状态得来,处在首列的状态只能从其上边的状态得来,因此首行i = 0 和首列的 j = 0 是边界条件。

如图所示,由于每个格子是由其左方格子和上方格子转移而来,因此我们使用循环来遍历矩阵,外循环遍历各行,内循环遍历各列。

解析:

根据对上面的理解我们已经可以写出动态规划的代码。然而子问题的解决是一种从顶至底的思想,因此按照“暴力搜索”->“记忆化搜索”->“动态规划”的顺序实现符合思维习惯。

方法一:暴力搜索

从状态[i,j]开始搜索,不断分解为更小的状态[i-1,j]和[i,j-1],递归函数包括以下的要素。

  1. 递归参数:状态[i,j]。
  2. 返回值:从[0,0]到[i,j]的最小路径和dp[i,j]。
  3. 终止条件:当 i = 0 且 j = 0 时,返回代价grid[0,0]。
  4. 剪枝:当i<0时或j<0时索引越界时,此时返回代价+∞,代表不可行。
实现代码如下所示
# python 代码示例
def min_path_sum_dfs(grid, i, j) :if i == 0 and j == 0 :return grid[0][0]if i < 0 or j < 0 :return infup = min_path_sum_dfs(grid, i - 1, j)left = min_path_sum_dfs(grid, i, j - 1)return min(up, left) + grid[i][j]
// c++ 代码示例
int minPathSumDFS(vector<vector<int>> &grid, int i, int j)
{if (i == 0 && j == 0){return grid[0][0] ;}    if (i < 0 or j < 0){retunr INT_MAX ;}int up = minPathSumDFS(grid, i - 1, j) ;int left = minPathSumDFS(grid, i, j - 1) ;return min(up, left) + gird[i][j] ;
}

解析:

给出一个dp[2,1]为根节点的递归树,其中包含一些重叠子问题,其数量会随着网格grid的尺寸变大而急剧增多。

造成重叠子问题的原因:存在多条路径可以从左上角到达某一单元格。

每个状态都有向下和向右两种选择,从左上角走到右下角总共需要m+n-2步,所以最差的时间复杂度为O(2^(m+n))。这种计算方法并没有考虑网格的边界情况,当到达网格边界时只剩下一种选择,因此实际的路径会少一些。

方法二:记忆化搜索

引入一个与grid网格大小相同的记忆列表mem,用于记录各个子问题的解,并将重叠子问题进行剪枝。

代码示例:
// c++ 代码示例
def min_path_sum_dfs_mem(grid, mem, i, j) :if i == 0 and j == 0 :return grid[0][0]if i < 0 or j < 0 :return infif mem[i][j] != -1 :return mem[i][j]up = min_path_sum_dfs_mem(grid, mem, i - 1, j)left = min_path_sum_dfs_mem(grid, mem, i, j - 1)mem[i][j] = min(up, left) + grid[i][j]return mem[i][j]
// c++ 代码示例
int minPathSumDFSMem(vector<vector<int>> &grid, vector<vector<int>> &mem, int i, int j) {if (i == 0 && j == 0) {return grid[0][0] ;}if (i < 0 || j < 0) {return INT_MAX ;}if (mem[i][j] != -1) {return mem[i][j] ;}int up = minPathSumDFSMem(grid, mem, i - 1, j) ; int left = minPathSumDFSMem(grid, mem, i, j - 1) ;mem[i][j] = min(left, up) != INT_MAX ? min(left, up) + grid[i][j] : INT_MAX ;return mem[i][j] ;
}

解析:

在引入记忆化搜索之后,所有的子问题只需要计算一次,因此时间复杂度取决于状态总数,即网格尺寸O(m*n)

方法三:动态规划

基于迭代实现动态规划,代码如下所示:

# pyhton 代码示例
def min_path_sum_dp(grid) :n, m = len(grid), len(grid[0])dp = [ [0] * m for _ in range(n)]dp[0][0] = grid[0][0]for j in range(1, m) :dp[0][j] = dp[0][j - 1] + grid[0][j]for i in range(1, n) :dp[i][0] = dp[i - 1][0] + grid[i][0]for i in range(1, n) :for j in range(1, m) :dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + gird[i][j]return dp[n - 1][m - 1]
int minPathSumDP(vector<vector<int>> &grid) {int n = grid.size(), m = grid[0].size();vector<vector<int>> dp(n, vector<int>(m));dp[0][0] = grid[0][0];for (int j = 1; j < m; j++) {dp[0][j] = dp[0][j - 1] + grid[0][j];}for (int i = 1; i < n; i++) {dp[i][0] = dp[i - 1][0] + grid[i][0];}for (int i = 1; i < n; i++) {for (int j = 1; j < m; j++) {dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j];}}return dp[n - 1][m - 1];
}

动态规划的过程如下所示:便利了整个网络,因此时间复杂度为O(m*n)。

数组的dp的大小也为m * n ,因此空间复杂度为O(m*n)。

空间优化

由于每个格子只与左边和上边的格子有关,我们可以只用一个单行数组来实现dp表。

关键:数组dp只能表示一行的状态,我们无法提前初始化首行的状态,而是在遍历每行时更新它。

代码如下:
# python 代码示例def min_path_sum_dp_comp(grid : list[list[int]]) -> int :n, m = len(grid), len(grid[0])dp = [0] * mdp[0] = grid[0][0]for j in range(1, m) :dp[j] = dp[j - 1] + grid[0][j]for i in range(1, n) :dp[0] = dp[0] + grid[i][0]for j in range(1, m) :dp[j] = min(dp[j - 1], dp[j]) + grid[i][j]return dp[m - 1]
// c++ 代码示例
int minPathSumDPComp(vector<vector<int>> &grid)
{int n = grid.size(), m = grid[0].size() ;vector<int> dp(m) ;dp[0] = grid[0][0] ;for (int j = 1 ; j < m ; j++){dp[j] = dp[j - 1] + gird[0][j] ;}for (int i = 1 ; i < n ; i++){dp[0] = dp[0] + grid[i][0] ;for (int j = 1 ; j < m ; j++){dp[j] = min(dp[j - 1], dp[j]) + grid[i][j] ;}}return dp[m- 1] ;
}

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

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

相关文章

C++进阶:继承和多态

文章目录 ❤️继承&#x1fa77;继承与友元&#x1f9e1;继承和静态成员&#x1f49b;菱形继承及菱形虚拟继承&#x1f49a;继承和组合 ❤️多态&#x1fa77;什么是多态&#xff1f;&#x1f9e1;多态的定义以及实现&#x1f49b;虚函数&#x1f49a;虚函数的重写&#x1f499…

MySQL之基本查询(上)-表的增删查改

目录 Create(创建) 案例建表 插入 单行数据 指定列插入 单行数据 全列插入 多行数据 全列插入 插入是否更新 插入时更新 替换 Retrieve(读取) 建表插入 select列 全列查询 指定列查询 查询字段为表达式 为查询结果指定别名 结果去重 where条件 比较运算符 逻辑运…

【深度学习基础】MacOS PyCharm连接远程服务器

目录 一、需求描述二、建立与服务器的远程连接1. 新版Pycharm的界面有什么不同&#xff1f;2. 创建远程连接3. 建立本地项目与远程服务器项目之间的路径映射4.设置保存自动上传文件 三、设置解释器总结 写在前面&#xff0c;本人用的是Macbook Pro&#xff0c; M3 MAX处理器&am…

opencv读取视频文件夹内视频的名字_时长_帧率_分辨率写入excel-cnblog

看视频的时候有的视频文件名贼长。想要翻看&#xff0c;在文件夹里根本显示不出来&#xff0c;缩短又会丢失一些信息&#xff0c;所以我写了一份Python代码&#xff0c;直接获取视频的名字&#xff0c;时长&#xff0c;帧率&#xff0c;还有分辨率写到excel里。 实际效果如下图…

uniapp父页面调用子页面 组件方法记录

文章目录 导文如何点击父页面&#xff0c;触发子页面函数先写一个子页面的基础内容父元素 如何点击父页面&#xff0c;修改子页面的值先写一个子页面的基础内容父元素 导文 如何点击父页面&#xff0c;触发子页面函数&#xff1f; 如何点击父页面&#xff0c;修改子页面的值&am…

Java基础语法--基本数据类型

Java基础语法–基本数据类型 Java是一种静态类型语言&#xff0c;这意味着每个变量在使用前都必须声明其数据类型。Java提供了多种基本数据类型&#xff0c;用于存储整数、浮点数、字符和布尔值等。以下是Java中的基本数据类型及其特点&#xff1a; 1. 整型&#xff08;Integ…

offer题目33:判断是否是二叉搜索树的后序遍历序列

题目描述&#xff1a;输入一个整数数组&#xff0c;判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回true,否则返回false。假设输入的数组的任意两个数字都互不相同。例如&#xff0c;输入数组{5,7,6,9,11,10,8},则返回true,&#xff0c;因为这个整数是下图二叉搜索树…

如何从 Vue 2 无痛升级到 Vue 3,一文搞定!

大家好,我是CodeQi! 一位热衷于技术分享的码仔。 随着 Vue 3 的发布,许多开发者都面临着从 Vue 2 升级到 Vue 3 的挑战。 本文将详细介绍如何从 Vue 2 无痛升级到 Vue 3,包括每个步骤的详细说明与代码示例。 让我们开始吧! 准备工作 在正式开始升级之前,请确保你已经…

C基础day7

一、思维导图 二、课后练习 1、提示并输入一个字符串&#xff0c;统计该字符串中字母、数字、空格以及其他字符的个数 #include<myhead.h> #define M 20 int main(int argc, const char *argv[]) {int sum_a0,sum_b0,sum_c0,sum_d0;char str[M];printf("please en…

使用redis进行短信登录验证(验证码打印在控制台)

使用redis进行短信登录验证 一、流程1. 总体流程图2. 流程文字讲解&#xff1a;3.代码3.1 UserServiceImpl&#xff1a;&#xff08;难点&#xff09;3.2 拦截器LoginInterceptor&#xff1a;3.3 拦截器配置类&#xff1a; 4 功能实现&#xff0c;成功存入redis &#xff08;黑…

HTML5+CSS3小实例:响应式漫画网格布局

实例:响应式漫画网格布局 技术栈:HTML+CSS 效果: 源码: 【HTML】 <!DOCTYPE html> <html lang="zh-CN"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-sc…

Windows11配置WSL2支持代理上网

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、安装WSL2分发版二、配置步骤三、测试总结 前言 说起来本来这个功能我也不需要的&#xff0c;只是最近突然有个需求就顺便研究了下&#xff0c;WSL2默认的网…

Docker-compse的应用

1 docker-compose # 使用了docker 面临一个比较大的问题&#xff0c;如果一个djagno项目&#xff0c;使用mysql&#xff0c;redis&#xff0c;不要一次性把所有服务都放到一个容器中&#xff0c;每个服务一个容器&#xff0c;批量的管理多个容器&#xff0c;比较难以操作&…

KIVY Button¶

Button — Kivy 2.3.0 documentation Button Jump to API ⇓ Module: kivy.uix.button Added in 1.0.0 The Button is a Label with associated actions that are triggered when the button is pressed (or released after a click/touch). To configure the button, the s…

vue 切换主题色切换主题色切换主题色切换主题色切换主题色

第一种&#xff1a;使用CSS变量 CSS变量&#xff08;Custom Properties&#xff09;是CSS的一种新特性 1.实现需求&#xff1a;自定义颜色 定义变量 全局的theme.css :root {--primary-color:red; }在组件中使用这些变量 demo.vue <template><div class"main…

Adversarial Reweighting for Partial Domain Adaptation(论文阅读)

摘要 1、问题 通过实验发现如今的PDA方法在利用重新调整对齐分布来使适应特征对于源域数据的“噪声”权重&#xff0c;在很多挑战基准测试点上会导致域的负迁移。 2、目的 对抗性调整&#xff08;AR&#xff09;方法&#xff1a;对抗性学习源域数据的权重去对齐源域和目标域的…

SVM - 径向基函数核 Radial Basis Function Kernel,简称RBF核或者高斯核

SVM - 径向基函数核 Radial Basis Function Kernel&#xff0c;简称RBF核或者高斯核 flyfish 径向基函数核&#xff08;Radial Basis Function Kernel&#xff0c;简称RBF核&#xff09;&#xff0c;也称为高斯核&#xff0c;是一种常用的核函数&#xff0c;用于支持向量机&a…

计算理论复习

1.Turing Machine 确定性图灵机 图灵机有很多不同的定义&#xff0c;这里选取其中一种&#xff0c;其它定义下的图灵机往往与下面这种定义的图灵机计算能力等价。 图灵机是一个在一条可双向无限延伸且被划分为若干格子的纸带上进行操作的机器&#xff0c;其有内部状态&#…

【高校科研前沿】中国农业大学姚晓闯老师等人在农林科学Top期刊发表长篇综述:深度学习在农田识别中的应用

文章简介 论文名称&#xff1a;Deep learning in cropland field identification: A review&#xff08;深度学习在农田识别中的应用&#xff1a;综述&#xff09; 第一作者及单位&#xff1a;Fan Xu&#xff08;中国农业大学土地科学与技术学院&#xff09; 通讯作者及单位&…

Linux:进程间通信(二.共享内存详细讲解以及小项目使用和相关指令、消息队列、信号量)

Linux&#xff1a;进程间通信&#xff08;二.共享内存详细讲解以及小项目使用和相关指令、消息队列、信号量&#xff09; 上次结束了进程间通信一&#xff1a;Linux&#xff1a;进程间通信&#xff08;一.初识进程间通信、匿名管道与命名管道、共享内存&#xff09; 文章目录 …