【三】【C语言\动态规划】珠宝的最高价值、下降路径最小和、最小路径和,三道题目深度解析

news2025/1/31 19:49:37

动态规划

动态规划就像是解决问题的一种策略,它可以帮助我们更高效地找到问题的解决方案。这个策略的核心思想就是将问题分解为一系列的小问题,并将每个小问题的解保存起来。这样,当我们需要解决原始问题的时候,我们就可以直接利用已经计算好的小问题的解,而不需要重复计算。

动态规划与数学归纳法思想上十分相似。

数学归纳法:

  1. 基础步骤(base case):首先证明命题在最小的基础情况下成立。通常这是一个较简单的情况,可以直接验证命题是否成立。

  2. 归纳步骤(inductive step):假设命题在某个情况下成立,然后证明在下一个情况下也成立。这个证明可以通过推理推断出结论或使用一些已知的规律来得到。

通过反复迭代归纳步骤,我们可以推导出命题在所有情况下成立的结论。

动态规划:

  1. 状态表示:

  2. 状态转移方程:

  3. 初始化:

  4. 填表顺序:

  5. 返回值:

数学归纳法的基础步骤相当于动态规划中初始化步骤。

数学归纳法的归纳步骤相当于动态规划中推导状态转移方程。

动态规划的思想和数学归纳法思想类似。

在动态规划中,首先得到状态在最小的基础情况下的值,然后通过状态转移方程,得到下一个状态的值,反复迭代,最终得到我们期望的状态下的值。

接下来我们通过三道例题,深入理解动态规划思想,以及实现动态规划的具体步骤。

LCR 166. 珠宝的最高价值

题目解析

状态表示

我们可以定义dp[i][j]表示,从(0,0)位置出发,到达(i,j)位置,在所有的可能路径中,所能获得的最大价值数。

状态转移方程

要到达(i,j)位置,要么从(i,j-1)位置往右走一步,要么从(i-1,j)位置往下走一步。根据状态表示,dp[i][j]表示从(0,0)位置出发,到达(i,j)位置,在所有的可能路径中,所能获得的最大价值数。dp[i][j-1]表示从(0,0)位置出发,到达(i,j-1)位置,在所有的可能路径中,所能获得的最大价值数。dp[i-1][j]表示从(0,0)位置出发,到达(i-1,j)位置,在所有的可能路径中,所能获得的最大价值数。

很简单可以得到dp[i][j]=max(dp[i][j-1],dp[i-1][j])+frame[i][j]

在这两种情况下,选择一个更大的价值,再加上自己位置上的价值,就是从(0,0)到达(i,j)位置的最大价值,也就是dp[i][j]

故,状态转移方程为:

dp[i][j]=max(dp[i][j-1],dp[i-1][j])+frame[i][j]

初始化

在填表的过程中,为了不造成越界访问的情况,我们需要对数组中某一些位置进行初始化。根据状态转移方程我们知道,如果要推导出(i,j)位置的状态,我们需要知道(i-1,j)和(i,j-1)位置的状态,所以在数组的第一行和第一列中,如果我们要推导这些状态,就会造成越界访问,所以需要对这些位置上的值进行初始化。

初始化具体操作是根据状态表示定义,依次填写正确的状态。

dp[i][j]表示,从(0,0)位置出发,到达(i,j)位置,在所有的可能路径中,所能获得的最大价值数。

所以第一行中,第一个数填自己的价值,第二个数填前一个数的价值加上自己的价值,以此类推,第一列同理。

我们会发现这样初始化会非常的复杂。

我们能不能把这些位置的计算通过状态转移方程进行推导得出来呢?如果直接进行推导数组一定会造成越界访问的错误,但我们依旧有办法可以把所有状态统一用状态转移方程推导出来,同时也不会造成越界访问的错误。

如果我们创建虚拟节点,将所有状态用状态转移方程进行推导,我们只需要初始化虚拟节点就可以了,这样做的好处是,对虚拟节点的初始化过程是十分简便的。

不过创建虚拟节点的操作有几点需要注意。

第一,对虚拟节点的初始化,必须能使所有状态正确推导。

第二,状态表示和状态转移方程需要改变对应关系。

意思是,dp[i][j]对应的位置在frame实际上是[i-1]j-1]位置。需要注意对应关系的改变。

接下来我们对虚拟节点进行初始化。

根据状态转移方程我们知道,(i,j)的状态需要由(i,j-1)(i-1,j)两个位置的状态推导得到,所以我们需要对虚拟节点除(0,0)位置外,每一个值都进行初始化。

因为(1,1)位置需要用到(0,1)(1,0)位置的状态值,其他的位置推导同理。

重新回到状态转移方程,dp[i][j]=max(dp[i][j-1],dp[i-1][j])+frame[i-1][j-1]

对于(1,1)点,dp[1][1]应该是它本身的价值,所以max(dp[i][j-1],dp[i-1[j])的值应该要为零。故把dp[i][j-1]=dp[i-1][j]置零。

对于第一行其他的点,他们的上方位置的价值应该是零,因为到不了哪些位置,所以也置零。

对于第一列同理。(这里说的第一行第一列是对于紫色框框来说的)

所以我们可以统一将dp数组置0,即可。

所以初始化是将dp数组全部置零。

填表顺序

从左到右,从上到下

返回值

返回最后一个值即可,即:dp[row][col]

代码实现

 
int jewelleryValue(int** frame, int frameSize, int* frameColSize) {
    int row=frameSize;
    int col=frameColSize[0];

    int dp[row+1][col+1];
    for(int i=0;i<=row;i++){
        memset(dp[i],0,sizeof(dp[i]));
    }
    

    for(int i=1;i<=row;i++){
        for(int j=1;j<=col;j++){
            dp[i][j]=fmax(dp[i-1][j],dp[i][j-1])+frame[i-1][j-1];
        }

    }

    return dp[row][col];
}

931. 下降路径最小和

题目解析

状态表示

我们可以定义dp[i][j]表示,从开头某一位置出发,到达(i,j)位置时,下降路径的最小和。

状态转移方程

对于(i,j)位置,需要得到,从开头某一位置出发,到达(i,j)位置时,下降路径的最小和。

我们想一想能不能由其他状态推导出(i,j)位置状态的值。

到达(i,j)位置一共有三种情况,要么从(i-1,j-1)位置往右下走一步,要么从(i-1,j)位置往下走一步,要么从(i-1,j+1)位置往左下走一步。

dp[i-1][j-1]表示从开头某一位置出发,到达(i-1,j-1)位置时,下降路径的最小和。

dp[i-1][j]表示从开头某一位置出发,到达(i-1,j)位置时,下降路径的最小和。

dp[i-1][j+1]表示从开头某一位置出发,到达(i-1,j+1)位置时,下降路径的最小和。

所以很容易得到,dp[i][j]=min(dp[i-1][j-1],dp[i-1][j],dp[i-1][j+1])+matrix[i][j];

故,状态转移方程为:

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

初始化

根据状态转移方程,我们需要推导(i,j)位置的值,需要运用(i-1,j-1),(i-1,j),(i-1,j+1)的值。

所以我们必须初始化第一行,第一列,和最后一列的数据。

我们发现这样的初始化操作十分的复杂。

我们希望简化这种复杂的操作。

所以我们把这些需要初始化的位置用虚拟节点代替,而原先需要初始化的位置改变为初始化虚拟节点。

我们知道初始化虚拟节点一般都是比较轻松的。

我们选择多添加一行和两列作为虚拟节点。将所有我们需要填写的状态位置都通过状态转移方程推导得到,这样我们就只需要初始化虚拟节点就可以了。

创建虚拟节点有几点注意事项:

第一,虚拟节点的初始化必须保证后续推导过程求值得准确性。

第二,注意下表得映射关系,也就是状态表示和状态转移方程的下标需要偏移。

接下来我们通过实际的含义,初始化虚拟节点里面的值。

关注我们需要填写的状态的第一行,也就是紫色框框圈住的第一行。

对于这些数据,我们需要用到蓝色第一行的数据,由状态转移方程,dp[i][j]=min(dp[i-1][j-1],dp[i-1][j],dp[i-1][j+1])+matrix[i-1][j-1];,实际上紫色的一行填写的数据一个就是matrix[i-1][j-1]

所以min(dp[i-1][j-1],dp[i-1][j],dp[i-1][j+1])值一个要为零。

所以蓝色第一行我们初始化为零。

关注我们需要填写的状态的第一列,也就是紫色第一列。

除第一个元素我们考虑过,其他的元素都需要用到蓝色第一列的数据。

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

在三者去最小的时候,我们不能取到dp[i-1][j-1]位置的值,所以这些位置应该初始化无穷大。

最后一列的初始化同理。

所以初始化操作为:

蓝色第一行全部初始化为零,第一列和最后一列初始化为无穷大。

填表顺序

从左往右,从上往下

返回值

我们需要得到从最上面开始出发,到达最下面的路径最小和,所以我们需要访问最后一行的元素,找到他们的最小值返回即可。

代码实现

 
int minFallingPathSum(int** matrix, int matrixSize, int* matrixColSize) {
    int row=matrixSize;
    int col=matrixColSize[0];

    int dp[row+1][col+2];
    for(int i=0;i<=row;i++){
        memset(dp[i],0,sizeof(dp[i]));
    }
    for(int i=1;i<=row;i++){
        dp[i][0]=0x3f3f3f;
        dp[i][col+1]=0x3f3f3f;
    }

    for(int i=1;i<=row;i++){
        for(int j=1;j<=col;j++){
            dp[i][j]=fmin(dp[i-1][j-1],fmin(dp[i-1][j],dp[i-1][j+1]))+matrix[i-1][j-1];

        }
    }

    int ret=INT_MAX;
    for(int i=1;i<=col;i++){
        ret=fmin(ret,dp[row][i]);
    }
    return ret;
}

我们首先把所有的数据初始化为零。然后再初始化第一列和最后一列的元素,初始化为无穷大。

这里我们可以用INT_MAX表示,也可以用0x3f3f3f3f表示。

0x3f3f3f3f是一个很有用的数值,它是满足以下两个条件的“最大整数”。

1、整数的两倍不超过 0x7f7f7f7f,即int能表示的最大正整数。

2、整数的每8位(每个字节)都是相同的。

我们在程序设计中经常需要使用 memset(a, val, sizeof a) 初始化一个数组a,该语句把数值 val(0x00~0xFF)填充到数组a 的每个字节上,所以用memset只能赋值出“每8位都相同”的 int。

当需要把一个数组中的数值初始化成正无穷时,为了避免加法算术上溢出或者繁琐的判断,我们经常用 memset(a, 0x3f, sizeof(a)) 给数组赋 0x3f3f3f3f的值来代替。

0x3f3f3f3f通常用于表示无穷大。

64. 最小路径和

题目解析

状态表示

我们可以定义,dp[i][j]表示,从(0,0)位置出发,到达(i,j)位置的所有情况下最小路径和。

状态转移方程

我们想一想,(i,j)位置的状态能不能由其他的位置推导得出来?

如果我们要到达(i,j)位置,要么从(i,j-1)位置向右走一步,要么从(i-1,j)位置向下走一步,dp[i][j]表示,从(0,0)位置出发,到达(i,j)位置的所有情况下最小路径和。

dp[i-1][j]表示,从(0,0)位置出发,到达(i-1,j)位置的所有情况下最小路径和。

dp[i][j-1]表示,从(0,0)位置出发,到达(i,j-1)位置的所有情况下最小路径和。

很容易可以得到,dp[i][j]=min(dp[i-1][j],dp[i][j-1])+grid[i][j]

故,状态转移方程为:

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

初始化

根据状态转移方程,我们知道,如果要推导出(i,j)位置的状态值,我们需要用到(i,j-1)和(i-1,j)位置的状态值,所以我们需要初始化第一列和第一行的所有数据的值。当然我们也可以添加虚拟节点,使得所有状态值都可以用状态转移方程推导得出,将所有的状态进行统一化,这样我们就不用初始化第一行和第一列的状态,转而初始化虚拟节点的状态就可以了,而初始化虚拟节点的值是比较容易实现的。因为我们要使得原先第一行和第一列的数据可以有前驱使得可以利用状态转移方程推导得出,所以我们需要添加一列和一行。

添加虚拟节点有几点注意事项:

第一,初始化虚拟节点的值,必须保证正确性,不能使得后续状态的推导出错。

第二,注意dp数组下标和原数组下标的映射关系,也就是状态表示和状态转移方程的下标映射关系。

我们目光回到红色框框住的一行和一列。

对于这些数据我们需要访问虚拟节点的值,推导出自己的状态值,状态转移方程是,

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

对于(1,1)位置的状态,实际上他的值应该是他自己,根据状态转移方程,min(dp[0][1],dp[1][0])的值应该为0

对于行来说,除去第一个数据,其他的状态推导中不能取虚拟节点的值,所以上面虚拟节点的值应该为正无穷。

对于列来说,同理,左边虚拟节点的值也应该为正无穷。

所以我们可以把所有数据都初始化正无穷,dp[0][1]和dp[1][0]位置选一个置0即可。

填表顺序

从左往右,从上往下

返回值

返回dp[row][col]最后一个数据的值。

代码实现

 
int minPathSum(int** grid, int gridSize, int* gridColSize) {
    int row=gridSize;
    int col=gridColSize[0];

    int dp[row+1][col+1];
    for(int i=0;i<=row;i++){
        memset(dp[i],0x3f3f3f3f,sizeof(dp[i]));
    }

    dp[0][1]=0;

    for(int i=1;i<=row;i++){
        for(int j=1;j<=col;j++){
            dp[i][j]=fmin(dp[i-1][j],dp[i][j-1])+grid[i-1][j-1];
        }
    }

    return dp[row][col];
}

0x3f3f3f3f是一个很有用的数值,它是满足以下两个条件的“最大整数”。

1、整数的两倍不超过 0x7f7f7f7f,即int能表示的最大正整数。

2、整数的每8位(每个字节)都是相同的。

我们在程序设计中经常需要使用 memset(a, val, sizeof a) 初始化一个数组a,该语句把数值 val(0x00~0xFF)填充到数组a 的每个字节上,所以用memset只能赋值出“每8位都相同”的 int。

当需要把一个数组中的数值初始化成正无穷时,为了避免加法算术上溢出或者繁琐的判断,我们经常用 memset(a, 0x3f, sizeof(a)) 给数组赋 0x3f3f3f3f的值来代替。

0x3f3f3f3f通常用于表示无穷大。

结尾

今天我们学习了动态规划的思想,动态规划思想和数学归纳法思想有一些类似,动态规划在模拟数学归纳法的过程,已知一个最简单的基础解,通过得到前项与后项的推导关系,由这个最简单的基础解,我们可以一步一步推导出我们希望得到的那个解,把我们得到的解依次存放在dp数组中,dp数组中对应的状态,就像是数列里面的每一项。最后感谢您阅读我的文章,对于动态规划系列,我会一直更新,如果您觉得内容有帮助,可以点赞加关注,以快速阅读最新文章。

最后,感谢您阅读我的文章,希望这些内容能够对您有所启发和帮助。如果您有任何问题或想要分享您的观点,请随时在评论区留言。

同时,不要忘记订阅我的博客以获取更多有趣的内容。在未来的文章中,我将继续探讨这个话题的不同方面,为您呈现更多深度和见解。

谢谢您的支持,期待与您在下一篇文章中再次相遇!

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

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

相关文章

docusaurus简介及使用心得

docusaurus简介 Docusaurus 是 Facebook 专门为开源项目开发者提供的一款易于维护的静态网站创建工具&#xff0c;使用 Markdown 即可更新网站。构建一个带有主页、文档、API、帮助以及博客页面的静态网站&#xff0c;只需5分钟。 同类竞品还有vuepress&#xff0c;docusaurus…

2.3_2 进程互斥的软件实现方法

2.3_2 进程互斥的软件实现方法 #mermaid-svg-MEJSSglXzFe6Q501 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-MEJSSglXzFe6Q501 .error-icon{fill:#552222;}#mermaid-svg-MEJSSglXzFe6Q501 .error-text{fill:#5522…

Python实现AR协方差结构线性回归模型(GLSAR算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 GLSAR是具有AR协方差结构的广义最小二乘法线性回归模型。 本项目通过GLSAR回归算法来构建AR协方差结构…

Postman接口测试(超详细整理)

常用的接口测试工具主要有以下几种 Postman&#xff1a;简单方便的接口调试工具&#xff0c;便于分享和协作。具有接口调试&#xff0c;接口集管理&#xff0c;环境配置&#xff0c;参数化&#xff0c;断言&#xff0c;批量执行&#xff0c;录制接口&#xff0c;Mock Server, …

Rust学习:HelloWorld

Rust学习&#xff1a;HelloWorld HelloWorldRust语言简介主要特点先看程序分析程序 HelloWorld Rust语言简介 Rust是一种系统编程语言&#xff0c;旨在提供内存安全、并发性和性能。它由Mozilla Research开发&#xff0c;旨在解决C和C语言中的一些关键问题&#xff0c;特别是…

STM32的以太网外设+PHY(LAN8720)使用详解(7):以太网数据接收及发送测试

0 工具准备 1.野火 stm32f407霸天虎开发板 2.LAN8720数据手册 3.STM32F4xx中文参考手册 4.Wireshark1 以太网数据接收测试 1.1 以太网数据接收测试&#xff08;轮询&#xff09; 我们在主循环内轮询RX DMA描述符标志位查看是否接收到了数据&#xff0c;如果接收到了则将数据…

漏洞复现-大唐电信AC集中管理平台敏感信息泄漏漏洞(附漏洞检测脚本)

免责声明 文章中涉及的漏洞均已修复&#xff0c;敏感信息均已做打码处理&#xff0c;文章仅做经验分享用途&#xff0c;切勿当真&#xff0c;未授权的攻击属于非法行为&#xff01;文章中敏感信息均已做多层打马处理。传播、利用本文章所提供的信息而造成的任何直接或者间接的…

Baumer工业相机堡盟工业相机如何通过BGAPI SDK实现Raw格式的图像保存(C#)

Baumer工业相机堡盟工业相机如何通过BGAPI SDK实现Raw格式的图像保存&#xff08;C#&#xff09; Baumer工业相机Baumer工业相机通过SDK实现Raw格式的图像保存的技术背景通过SDK获取相机信息的代码分析Baumer工业相机回调函数里保存原始图像数据Baumer保存Raw图像格式重要核心代…

数据库开发之SQL简介以及DDL的详细解析

1.3 SQL简介 SQL&#xff1a;结构化查询语言。一门操作关系型数据库的编程语言&#xff0c;定义操作所有关系型数据库的统一标准。 在学习具体的SQL语句之前&#xff0c;先来了解一下SQL语言的语法。 1.3.1 SQL通用语法 1、SQL语句可以单行或多行书写&#xff0c;以分号结尾…

基于STM32的HC-SR501红外感应模块驱动与应用

一、 简介 HC-SR501红外感应模块是一种常用的人体红外感应模块&#xff0c;常用于安防监控、智能家居等领域。本文将介绍如何在STM32单片机上驱动和应用HC-SR501红外感应模块&#xff0c;实现基本的人体检测功能。 二、 模块原理 HC-SR501红外感应模块基于红外热释电传感器&am…

深眸科技以AI+3D视觉技术引领技术创新,赋予工业自动化新的活力

随着工业4.0和智能制造时代的到来&#xff0c;3D机器视觉在工业各领域的应用越来越重要。这种技术改变了传统工业的生产方式&#xff0c;为现代工业带来了更高的生产效率和更精确的质量控制&#xff0c;广泛涉及物体识别、产品检测、尺寸测量、视觉引导定位等环节。 在工业领域…

跨平台应用程序开发软件,携RAD Studio 12新版上线

RAD Studio 是一款专为程序员而准备的跨平台应用程序开发软件&#xff0c;内置Delphi和CBuilder这两种开发工具&#xff0c;另外还提供了新的C功能&#xff0c;扩展了对ExtJS的RAD服务器支持&#xff0c;增强了对vcL的高dpi支持&#xff0c;提高了firemonk (FMX)的质量等等&…

利用Matplotlib画简单的线形图

实验题目&#xff1a;简单的线形图 实验目的&#xff1a;利用Matplotlib画简单的线形图 实验环境&#xff1a;海豚大数据和人工智能实验室&#xff0c;使用的Python库 名称 版本 简介 numpy 1.16.0 线性代数 Pandas 0.25.0 数据分析 Matplotlib 3.1.0 数据可视化 …

ESP8266 TCP/串口透传

简介 先在PC上做测试, 使用串口软件对ESP8266 模块进行设置, 使用网络助手软件与串口软件进行自由收发设置 ATRST ## 复位 ATCWMODE_DEF1 ## 设置为Station模式 ATCWJAP_DEF“路由器wifi名称”,“路由器wifi密码” ## 设置ESP连接的路由器名称密码 ATCIPSTART“TCP”,“192.1…

鸿蒙和各大厂合作,是不是要火起来

今年9月底&#xff0c;在华为秋季全场景新品发布会上&#xff0c;华为常务董事、终端BG CEO余承东宣布&#xff0c;鸿蒙原生应用全面启动&#xff0c;HarmonyOS NEXT开发者预览版将在2024年第一季度开放。 近日&#xff0c;腾讯、阿里、美团、网易&#xff0c;外包大厂中软国际…

评论回复功能数据库设计

1. 评论的场景 类似csdn博客评论 2. 建表sql CREATE TABLE comment (id varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT id,parent_id varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 父级评论id&#xff08;…

【原理图PCB专题】原理图图纸锁定/解锁与PCB文件加密方式

在工作中我们会遇到需要冻结原理图进行评审和加密图纸防止被他人盗用的需求。那么在OrCAD Capture中如何对图纸进行锁定与解锁,如何在Allegro中对PCB工程进行加密呢? 原理图锁定与解锁 打开原理图,在图纸中单击右键,选择lock/unlock就可以进行锁定与解锁。 锁定时图纸图…

C语言中常用的sscanf函数

文章目录 1. 接受全部参数&#xff1a;2、分辨数字和字符3. 数字和字符一起会默认是字符4. 同时接收多个变量5. 指定长度的集合操作6. 排除部分字符 sscanf()定义于头文件stdio.h。sscanf()会将参数str的字符串根据参数format字符串来转换并格式化数据。格式转换形式请参考scan…

网安面试三十道题(持续更新)(sql注入系列)

61 给你一个网站&#xff0c;一般怎么做渗透测试的 先确定黑盒测试还是白盒测试 黑盒测试 信息收集&#xff1a; 服务器相关---&#xff1a;系统版本&#xff0c;真实IP&#xff0c;开放端口&#xff0c;使用的中间件 指纹信息---有无cdn加速&#xff0c;dns解析记录&#xff0…

MailChecker:一款功能强大的跨语言临时电子邮件安全检测库

关于MailChecker MailChecker是一款功能强大的跨语言临时电子邮件安全检测工具&#xff0c;该工具可以帮助广大研究人员快速对目标电子邮件进行安全检测和内容验证。该工具后端由一个包含了超过55000个的虚假电子邮件提供商的数据库驱动&#xff0c;当你需要使用电子邮件与你的…