动态规划之-不同路径 II-滚动数组_20230421

news2024/11/16 9:00:27

DP动态规划之-滚动数组

  1. 前言

在学习 不同路径II 的动态规划过程中,从介绍资料中了解到 滚动数组可以进一步降低动态规划解空间的复杂度,更高效利用计算机的储存空间。动态规划中的滚动数组究竟能发挥哪些作用,在常规的动态规划中,我们又是如何进行具体的应用呢?

从本质上理解,滚动数组的作用就是为了更好利用储存空间,用有限的空间不断进行覆盖,从而完成整体算法任务,具体而言分为两类:

  • 一维数组简化为2个或3个变量
  • 二维数组简化为1维数组
  1. 一维数组转化为滚动数组

具体看一个斐波那契数列的例子,中规中矩的动态规划数组一般设定为dp[n+1],其中n表示斐波那契数列的下标(位置),通过dp[n]=dp[n-1]+dp[n-2]求解出所有的斐波那契数列。

动态规划的基础解为dp[1]=1, dp[2]=1,然后通过循环求出每个位置上的斐波那契数字,最后返回dp[n]即为所求得的具体解。

int fib_normal_array(int n)
{
    int i;
    int dp[n+1];

    dp[1]=1;
    dp[2]=1;

    for(i=3;i<=n;i++)
    {
        dp[i]=dp[i-1]+dp[i-2];
    }

    return dp[n];
}

那么如何转化为滚动数组呢? 通过观察我们发现,对于每个位置上的斐波那契数字,它仅仅与前面两个相邻的斐波那契数字相关,再往前的斐波那契数字与它无关。这就给我们些微的思路,尝试探索用3个单独变量来滚动实现斐波那契数字的求解。假定f1,f2和f3为连续的三个斐波那契数字,f3=f1+f2, 接下来把f2赋值给f1, f3赋值给f2,神奇的事情就会发生,让我们看具体的代码实现。

int fib_scrolling_array(int n)
{
    int i;
    int f1;
    int f2;
    int f3;

    f1=1;
    f2=1;

    for(i=3;i<=n;i++)
    {
        f3=f1+f2;
        f1=f2;
        f2=f3;
    }

    return f3;
}

用图来进一步解释说明,可以观察出,f2和f3不断滚动到f1和f2, 周而复始,最终求得f3具体值,由于新值与旧值不断交换,形象称之为滚动。
在这里插入图片描述

上述例子可以看出,通过合理的变量设定,可以把占用空间从n降低为3个变量,但是工程问题永远都有两面性,减少了储存空间,那么如果要查找,就只能找到至多三个斐波那契数字,如果设定的一维数组,那么就可以通过下标任意查询。具体采用哪种模式,需要根据具体问题具体分析。

  1. 二维数组简化为一维数组

很多情况下,动态规划问题的求解有赖于DP二维数组,通过dp[i][j]与前面已知求得的解进行关联求和或求布尔运算,从而求出dp[i][j]的具体值。

看一个具体例子,前面曾经提到的 不同路径II的问题,常规情况下,利用二维DP数组进行求解,如果采用滚动数组的概念,则可以把二维数组优化为一维数组,如果进一步深入理解,也就是把二维数组的n×m个变量空间优化为m个变量空间,从形式上直观理解,二维数组自然退化为一维数组。

先给出 不同路径II的问题描述,问题引用自leetcode.

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish”)。现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?网格中的障碍物和空位置分别用 10 来表示。
在这里插入图片描述

这个问题可以称之为条件式的动态规划问题,这源于障碍物的限制,障碍物限制会导致递归模式中的剪枝情况出现,本问题采用DP迭代动态规划,它会通过判断语句,对不同的dp[i]][j]进行赋值。

动态规划的重要特征是通过最优化子问题定义状态转移方程,可以理解为先定义最优化子问题,然后通过定义的子问题递归或迭代求出原问题的解。

对于此问题,题目要求求解最终不同路径的数目,同时限制了路径的走向,对于任意一个坐标点f(i,j),如果需要求出到达此点的路径的总数目,可以分两种情况:

  • 情况1,如果此处存在路障,那么f(i,j)=0, 用编程语言描述,如果路障数组在某处obstacleGrid[i][j]的值为1,那么这个点和之前任何点都没有关系,到达此处的路径总数为0
  • 情况2, 如果此处畅通无阻,这是可以把(i,j)看作是路径的终点,显而易见,f(i,j)和它相邻的做单元格和上单元格相关,进一步观察,实际上f(i,j)的路径总数取决于f(i-1,j) 和f(i,j-1)的数量之和,我们可以称作f(i-1,j)和f(i,j-1)为f(i,j)的子问题

从上面分析可以看出,采用动态规划最简单的方法就是定义一套二维数组,数组的坐标位置代表机器人所需要到达的目标位置,数组的值来储存到达此处的不同路径总数目。

对于DP值,需要找到它的基础值,也就是DAG路径当中的终点值。

在这里插入图片描述

对于第一行或第一列的障碍物数组,只要碰到出现障碍物,那么障碍物之后的不同路径总数目都为0,具体看实际例子,比如obstacle_grid[0][2]=1,那么它(包括本身)之后的在第一行中的DP数组的值(路径数目)都为0。同理对于obstacle_gride[0][3]的值为1(障碍物),那么它(包括本身)之后的所有第一列当中的DP数组的值均为0.
在这里插入图片描述

int unique_path(int **obstacle_grid, int row, int col)
{
    int i; //row index
    int j; //column index
    int dp[row][col];

    memset(dp,0,sizeof(dp));

    dp[0][0]=(obstacle_grid[0][0]==0);

    for(i=1;i<row;i++)
    {
        dp[i][0]=dp[i-1][0] && !(obstacle_grid[i][0]);
    }

    for(j=1;j<col;j++)
    {
        dp[0][j]=dp[0][j-1] && !(obstacle_grid[0][j]);
    }

    for(i=1;i<row;i++)
    {
        for(j=1;j<col;j++)
        {
            if(obstacle_grid[i][j]==0)
            {
                dp[i][j]=dp[i-1][j]+dp[i][j-1];
            }
            else
            {
                dp[i][j]=0; //depends on the current node isntead of previous node
            }            
        }
    }

    return dp[row-1][col-1];
}

void create_obstacle(int ***obstacle_grid, int row, int col)
{
    int i;
    int j;

    *obstacle_grid=(int **)malloc(sizeof(int *)*row);

    for(i=0;i<row;i++)
    {
        *(*obstacle_grid+i)=(int *)malloc(sizeof(int)*col);
        memset(*(*obstacle_grid + i),0,sizeof(int)*col);
    }
    //(*obstacle_grid)[1][1]=1
    return;
}
#endif

大家如果仔细观察,可以发现,如果当前位置上没有路障,那么DP储存的不同路径数目仅和其紧邻左侧和上侧值有关,这时候最朴素的理解是不是可以采用dp[m][2]来储存数据呢? 答案当然是可行的,用两行一维数组来储存,并且交替更新数据,可以得到所求的结果,没有任何问题。

再进一步深度思考,能否采用一维数组实现算法呢? 答案也是肯定的,只不过需要用图示来说明一下它的实际来历。假定要计算新的DP[3]值,那么如何从同一行中的数值进行计算呢,实际上很简单DP[3]=DP[3](原有的值,相当于上侧)+DP[2](原有的值,相当于左侧),通过在同一行数组中的“滚动”更新,从而求得答案。
在这里插入图片描述

具体代码实现:

int unique_path(int **obstacle_grid, int row, int col)
{
    //using the scrolling array/list
    int dp[col];
    int i;
    int j;
    memset(dp,0,sizeof(dp)); //need set the initial value as zero

    dp[0]=(obstacle_grid[0][0]==0);

    for(i=0;i<row;i++)
    {
        for(j=0;j<col;j++)
        {
            if(obstacle_grid[i][j]==1)
            {
                dp[j]=0;
            }
            else if((j-1)>=0 && obstacle_grid[i][j]==0)
            {
                dp[j]=dp[j]+dp[j-1];
            }
        }
    }

    return dp[col-1];
}

那么我们再进一步深入思考此问题,因为过程中实际上仅仅三个变量相关,那么能否采用与Fibonacci相同的3个变量实现方式呢?我们把答案留给读者思考,在此不再赘述。

  1. 小结

通过斐波那契和不同路径II问题的分析,勾勒出滚动数组两种常见的优化方式,其主要从时间复杂度上降低DP所占用的具体储存空间。由于其实现过程类似于“滚动”过程,我们形象称之为滚动数组。滚动数组实际上不止在动态规划中有应用,在线性链表遍历中,也可以采用类似概念,实现空间复杂度的降低。

参考资料:

63. 不同路径 II - 力扣(Leetcode)

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

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

相关文章

REDIS02_RDB概述及作用、自动触发、手动触发、优势劣势、触发场景、配置项详解

文章目录 ①. RDB概述及作用②. RDB - 自动触发③. 手动触发 - save、bgsave④. RDB - 优势体现⑤. RDB - 劣势体现⑥. 哪些情况会触发RDB快照⑦. RDB优化配置项详解 ①. RDB概述及作用 ①. RDB概述:在指定的时间间隔,执行数据集的时间点快照 实现类似照片记录效果的方式,就是…

MapReduce高级篇——全局计数器

MapReduce Counter 计数器 概念 在执行MapReduce程序的时候&#xff0c;控制台输出日志中通常下面片段&#xff0c;可以发现输出信息中的核心词是counter,中文叫做计数器 在执行MapReduce城西过程中&#xff0c;许多时候&#xff0c;用户希望了解程序的运行情况&#xff0c;H…

白话文讲计算机视觉-第十讲-灰度阈值分割

灰度阈值是啥意思呢&#xff1f;我们慢慢说。 1.灰度图 我们现在有一张彩色图&#xff0c;我们给它用黑白的方式变现&#xff0c;就形成灰度图&#xff0c;如图所示。 图1 那究竟怎么转换的呢&#xff1f;很简单&#xff0c;我们根据如下公式&#xff0c;把BGR三个通道换成一个…

【算法题解】26. 求串联子串的位置

这是一道 困难 题 来自&#xff1a; https://leetcode.cn/problems/substring-with-concatenation-of-all-words/ 题目 给定一个字符串 s 和一个字符串数组 words。 words 中所有字符串 长度相同。 s 中的 串联子串 是指一个包含 words 中所有字符串以任意顺序排列连接起来的…

REXROTH液压方向阀安装须知

安装规程 阀安装到系统之前&#xff0c;应该对照订货型号比较其型号说明。 确认阀的连接表面和底板无水分&#xff0c;没有油。 &#xff0d; 清洁&#xff1a; ‧ 安装元件时&#xff0c;确认工业阀和周围干净 ‧ 油箱须密闭&#xff0c;以防止外部污染 ‧ 安装之前&…

【youcans的深度学习 D02】PyTorch例程:创建 LeNet 模型进行图像分类

欢迎关注『youcans的深度学习』系列 【youcans的深度学习 D02】PyTorch例程&#xff1a;创建 LeNet 模型进行图像分类 1. PyTorch 深度学习建模的基本步骤2. 加载 CIFAR-10 数据集3. 定义 LeNet-5 模型类3.1 LeNet 网络3.2 LeNet-5 网络3.3 定义 LeNet-5 网络模型类3.4 构建网络…

AI大模型加速RPAxAI时代到来,谁会是RPA领域的杀手级应用?

GPT等AI大模型震撼来袭&#xff0c;基于RPA的超级自动化仍是最佳落地载体 对话弘玑CPO贾岿&#xff0c;深入了解国产RPA厂商对AI大模型的探索与实践 文/王吉伟 关于RPA已死的说法&#xff0c;在中国RPA元年&#xff08;2019年&#xff09;投资机构疯狂抢项目之时就已经有了。…

算法训练Day39:62.不同路径 63. 不同路径 II 动态规划

文章目录 不同路径题解(动态规划)数论方法 [不同路径 II](https://leetcode.cn/problems/unique-paths-ii/description/)题解 不同路径 CategoryDifficultyLikesDislikesContestSlugProblemIndexScorealgorithmsMedium (67.70%)17460--0 Tags Companies 一个机器人位于一个 …

Linux基础—网络设置

Linux基础—网络设置 一、查看网络配置1.查看网络接口信息 ifconfig2.查看主机名称 hostname3.查看路由表条目 route4.查看网络连接情况 netstat5.获取socket统计信息 ss 二、测试网络连接1.测试网络连接 ping2.跟踪数据包 traceroute3.域名解析 nslookup 三、使用网络配置命令…

拷贝构造与深浅拷贝

文章目录 一、拷贝构造函数二、拷贝初始化三、深浅拷贝 一、拷贝构造函数 如果一个构造函数的第一个参数是自身类型的引用&#xff0c;而且任何额外参数都有默认值&#xff0c;则此构造函数是拷贝构造函数。 class person { public: person(); //默认构造函数 pe…

54家备案法人信用评级机构名单

2023年4月20日&#xff0c;中国人民银行官网公示备案法人信用评级机构名单&#xff0c;共有54家机构获得了信用评级备案&#xff0c;并进行如下提示&#xff1a; 1.2019年11月26日&#xff0c;人民银行、发展改革委、财政部、证监会联合发布《信用评级业管理暂行办法》&#xf…

C语言之 顺序表(sequence chart)

线性表 线性表是n个具有相同特性的数据元素的有限序列。 常见的有&#xff1a;顺序表、链表、栈、队列、字符串.... 线性表在逻辑上是线性结构的&#xff0c;即一条连续的直线 但在物理结构上不一定是连续的&#xff0c;其在物理存储时&#xff0c;通常以数组和链式结构的形式…

观察者模式解读

目录 问题引进 天气预报项目需求 天气预报设计方案 1-普通方案 传统方式代码实现 观察者模式原理 观察者模式解决天气预报需求 代码实现 观察者模式的好处 问题引进 天气预报项目需求 1) 气象站可以将每天测量到的温度&#xff0c;湿度&#xff0c;气压等等以公告的形式…

idm下载器是免费的吗?有哪些功能

对于PC用户来说&#xff0c;拥有一款好用和快速的下载工具&#xff0c;对我们来说至关重要&#xff0c;可以极大提高我们的工作效率和PC用户体验。IDM可以实现高速下载&#xff0c;其核心原理就是多线程下载&#xff0c;理论上可以达到带宽的峰值速度&#xff0c;深受用户的喜爱…

Python单向链表操作

目录 一、单向链表 单向链表示例图 二、单向链表的操作 1、判断链表是否为空 2&#xff0c;链表长度 3&#xff0c;遍历整个链表 4&#xff0c;在链表头部添加元素 5、链表尾部添加元素 6&#xff0c;在指定位置插入元素 7&#xff0c;修改指定位置的元素 8&#xff…

可视化Echarts中title、tooltip、legend、grid属性的常用设置

title中常用的设置 配置项--tooltip ​编辑 配置项--legend 配置项--grid title中常用的设置 title 标题组件&#xff0c;包含主标题和副标题。 以下是常用的对标题的设置 title:{//设置图表的标题text:"主标题",link:"baidu.com", //设置标题超链接…

详解C语言string.h中常见的14个库函数(三)

本篇博客继续讲解C语言string.h头文件中的库函数。本篇博客计划讲解3个函数&#xff0c;分别是&#xff1a;strstr, strtok, strerror。其中strstr函数我会用一种最简单的方式模拟实现。 strstr char * strstr ( const char * str1, const char * str2 );strstr可以在str1中查…

用yolov5+playwright过滑动验证码

目录 梳理思路 训练模型 编写代码 总结与提高 源码下载 在上一节&#xff0c;我们通过opencv-pythonplaywright成功过掉了QQ空间的滑动验证码。在本节&#xff0c;我们将使用yolov5playwright来实现相同效果。 注&#xff1a;因为yolov5的配置教程网上已经很多了&#xff…

C++初阶之函数重载

目录 前言 函数重载 1.函数重载的概念 2.C支持函数重载的原理--名字修饰(name Mangling) 前言 今天小编给大家带来的是C中关于函数重载的内容&#xff0c;和C语言不一样&#xff0c;函数重载是C语言特有的&#xff0c;那么该功能实现的底层原理是什么呢&#xff1f;请大家…

Idea配置maven,指定settings.xml文件不生效

一.简介 最近单位要求把项目的仓库配置从阿里云改为nexus私服&#xff0c;配置了一个settings-nexus.xml的配置文件&#xff0c;idea的maven配置指定了该settings-nexus.xml文件&#xff0c;发现走的还是阿里云的&#xff0c;新的settings-nexus.xml竟然不生效&#xff0c;依赖…