【Hello Algorithm】暴力递归到动态规划(四)

动态规划的数组压缩技巧 - 机器人走格子问题

题目是leetcode62题目原题 表示如下

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

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

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

在这里插入图片描述

递归版本

我们首先来想递归函数的含义 它会返回给我们一个int类型的数据 这个数据就是我们的最大路径数

我们需要给这个函数 我们当前的位置 我们需要去到的位置 整体函数如下

int _uniquePaths(int x , int y ,int m , int n)

其中 x y 代表我们当前位置的坐标 m n代表要到达位置的坐标

接下来我们想base case

因为这是一个位置限制的棋盘 所以说我们要考虑是否会越界的问题 即

        if (x > m || y > n){return 0;}

当然 当我们的走到finish位置的时候也算是结束了 会返回给我们一种路径方法 表示如下

        if (x == m && y == n){return 1;}

接下来我们就开始列举各种可能性 因为我们这里只能往下或者是往右走 所以说一共有两种可能性

我们只需要把这两种可能性所需要的路径和相加就可以了 代码表示如下

    int _uniquePaths(int x , int y ,int m , int n){// base caseif (x > m || y > n){return 0;}if (x == m && y == n){return 1;}int p1 = _uniquePaths(x + 1 , y, m,  n);int p2 = _uniquePaths(x, y + 1, m,  n);return p1 + p2;}

动态规划

接下来我们开始动态规划版本的代码改写

首先我们找出一直变化的变量是什么

 int _uniquePaths(int x , int y ,int m , int n)

我们发现递归中一直变化的参数其实只有两个 x 和 y

所以说我们只需要建立一张x和y的二维表就可以

x的格子一共有m个 y的格子一共有n个 所以说 x的大小可以设置为 0 ~ m-1 y的大小可以设置为0 ~ n-1

我们要知道的是 x和y可能会越界 所以说我们要设置一个pickup函数来从表中选值 如果说越界了我们直接返回0即可

   int pickup_dp(int x , int y , int m , int n , vector<vector<int>>& dp){if (x > m || y > n){return 0;}return dp[x][y];}

接下来我们来看base case

        if (x == m && y == n){return 1;}

也就是说 当x为最大值 y为最大值的时候 此时dp表设置为1

dp[m-1][n-1] = 1;

接下来我们开始找位置依赖关系

        int p1 = _uniquePaths(x + 1 , y, m,  n);int p2 = _uniquePaths(x, y + 1, m,  n);

假设这个格子是表中任意一个 图中表示为黑色的格子

在这里插入图片描述
那么依赖的格子就是红色的

根据依赖关系 我们可以从右往左 从下往上的方式 来填写依赖关系 代码表示如下

    int dp_uniquePaths(int m , int n , vector<vector<int>>& dp){dp[m-1][n-1] = 1;for (int col = n -1 ; col >= 0 ; col--){for (int row = m -1; row >= 0; row--){if (row == m-1 && col == n-1){continue;}int p1 = pickup_dp(row + 1, col, m, n, dp);int p2 = pickup_dp(row , col + 1,  m,  n, dp);dp[row][col] = p1 + p2; }}return dp[0][0];}

这就是这道题目的动态规划解法

数组压缩技巧

我们可以发现的是 其实每个格子都只依赖于该列和它的右边一列 那么我们就可以使用两个列来表示整个二维表

也就是二维表转化为一维表 节省一定的空间

压缩技巧也很简单 只需要一列一列的转化就可以

代码表示如下

class Solution {
public:int pickup_dp(int x , int m , vector<int>& dp){if (x >= m || x < 0){return 0;}return dp[x];}int dp_uniquePaths(int m , int n ){// col1 prev // col2 curvector<int> col1(m , 0);vector<int> col2(m , 0);col1[m-1] = 1;for (int i = 0; i < n ; i++){for(int j = m - 1; j >= 0; j--){col2[j] = pickup_dp(j + 1, m, col2) + col1[j]; }for(int j = m -1 ; j >= 0 ; j--){col1[j] = col2[j];}}return col2[0];}int uniquePaths(int m, int n) {return dp_uniquePaths(m  , n );}
};

我们这里稍微讲解下两列的转化思路

我们设定 col1为前一列 col2为当前列

每次我们修改col2内部的值 到最后我们全部修改完毕要到下一列的时候 我们更新下col1列的所有值

钱包问题一

我们给定一个数组 arr 数组里面的值表示任意一张面值的钞票

每张钞票代表的值可能相同 但是我们认为它们是不同的钞票

现在我们给定一个 val 值 请问有多少种组合方案可以让val值为0

递归解法

还是一样 我们首先来想函数

它要返回给我们一个组合的最大值 所以说我们的返回值要是一个int类型的数值

我们要遍历整个money数组 所以说我们需要这个数组和一个index参数来遍历

接着我们需要一个rest参数来记录剩余零钱的数目

整体函数如下

int process(vector<int>& money , int index , int rest)

接下来我们开始想base case

这道题目中有两个变化的量 我们首先向有没有可能会因为index而终止递归呢?

当然有 如果index越界了 那么我们的递归也就终止了

有没有可能因为rest而终止递归呢 ?

当然有 如果剩余零钱的数目为0 我们就终止递归了

  if (rest < 0)    {    return 0;    }    int N = static_cast<int>(money.size());    if (index == N)    {    return rest == 0 ? 1 : 0;    }    

接下来开始列举可能性 对于这种从左往右的模型来说 可能性就是要和不要两种情况

所以说我们直接列出这两种可能性之后想加即可

int process(vector<int>& money , int index , int rest)    
{    if (rest < 0)    {    return 0;    }    int N = static_cast<int>(money.size());    if (index == N)    {    return rest == 0 ? 1 : 0;    }    int p1 = process(money , index + 1 , rest);    int p2 = process(money , index +1 , rest - money[index]);    return p1 + p2;    
} 

动态规划

我们首先观察递归函数

int process(vector<int>& money , int index , int rest)  

我们可以发现 其中变化的变量有 index 和 rest

所以说我们可以围绕着index 和 rest建立一张二维表

index 的大小是 0 ~ index 大小是index + 1

rest 的大小是 0 ~ rest 大小是rest + 1

我们建立完一个二维表之后就可以根据base case填写数据了

根据

 if (index == N)    {    return rest == 0 ? 1 : 0;    }    

我们可以得出

 dp[N][0] = 1;  

接着我们来看位置依赖关系

在这里插入图片描述

它依赖于下面一行的两个格子

所以说我们从最下面的倒数第二行开始填写数据 为了防止越界问题 我们再写一个pickup函数

完整代码如下

int dpprocess(vector<int>& money , int rest)    
{    int N = static_cast<int>(money.size());    vector<vector<int>> dp(N + 1 , vector<int>(rest + 1 , 0));    dp[N][0] = 1;    for (int row = N - 1;  row >= 0; row--){    for (int col = 0; col <= rest; col++)    {dp[row][col] = pickupdp(row + 1 , col , dp , N , rest) + pickupdp(row + 1 , col - money[row] , dp , N , rest);}}return dp[0][rest];
}

钱包问题二

我们给定一个数组 arr 数组里面的值表示任意一张面值的钞票 arr内部值不同

每一个arr中的元素代表有无数张钞票

现在我们给定一个 val 值 请问有多少种组合方案可以让val值为0


这个问题和问题一的区别就是 在问题2中 我们的钱包有无数张钞票 只是它们的面值不同 要我们求解法

递归版本

我们首先来想 我们要写什么样的一个递归函数

我们要让这个函数返回一个最大的组合方案 所以返回值是一个int类型的数据

而我们要在一个数组中选取数据 所以自然而然的想到使用index遍历

最后我们还需要一个rest来表示剩余值 整体表示如下

int process(vector<int>& money , int index , int rest)

接着就是想base case 这一步照抄钱包问题一即可

到了列举可能性的这一步就有点意思了

此时的问题就从要不要变为了两个问题

  • 要不要?
  • 要的话要几个

所以说我们的代码也要转变下

  int p1 = process(money , index + 1 , rest);    // how many ? int p2 = 0;    for (int fix = 1 ; fix * money[index] <= rest; fix++)    {    p2 += process(money , index + 1 , rest - fix * money[index]);    }   

可能性1就是我们不要这种类型的钞票了

可能性2就是我们要这种类型的钞票 一张张枚举 知道rest小于0为止

当然我们其实可以让fix从0开始 这样就不需要可能性1了

整体代码表示如下

int process(vector<int>& money , int index , int rest)
{if (rest < 0){return 0;}int N = static_cast<int>(money.size());if (index == N){return rest == 0 ? 1 : 0;}int p1 = process(money , index + 1 , rest);    // how many ? int p2 = 0;    for (int fix = 1 ; fix * money[index] <= rest; fix++)    {    p2 += process(money , index + 1 , rest - fix * money[index]);    }    return p1 + p2;    
}   

动态规划

我们首先观察递归函数

int process(vector<int>& money , int index , int rest)

我们可以发现 变量只有index 和rest

所以我们可以围绕着index和rest来做一张二维表

index 的大小是 0 ~ index 大小是index + 1

rest 的大小是 0 ~ rest 大小是rest + 1

我们建立完一个二维表之后就可以根据base case填写数据了

根据

 if (index == N)    {    return rest == 0 ? 1 : 0;    }    

我们可以得出

 dp[N][0] = 1;  

接下来我们来看为止依赖关系

在这里插入图片描述

我们可以发现这个位置依赖于下面一行的数据具体的格子数目不确定

所以说我们就可以写出这样子的代码

  for (int row = N - 1; row >= 0; row--)    {    for(int col = 0; col <= rest; col++)    {    int ways = 0;    for (int fix = 0; fix * money[row] <= rest; fix++)    {    ways += pickupdp(row + 1 , col - fix * money[row] , dp , N  , rest );    }    dp[row][col] = ways;                                                                                                        }    }    

动态规划优化

我们还是来观察下图

在这里插入图片描述

我们可以发现蓝色格子依赖的红色格子其实只比黑色格子依赖的红色格子少一个

也就是说我们可以这么转化

黑色格子依赖于蓝色格子和它下面的一个红色格子

于是我们的代码就可以这样子改写

int dpprocess(vector<int>& money , int rest)    
{    int N = static_cast<int>(money.size());    vector<vector<int>> dp(N + 1 , vector<int>(rest + 1 , 0));    dp[N][0] = 1;    for (int row = N - 1; row >= 0; row--)    {    for(int col = 0; col <= rest; col++)    {    dp[row][col] = pickupdp(row , col - money[row] , dp , N ,rest) + pickupdp(row + 1 , col , dp , N , rest);                   }    }    return dp[0][rest];    
}  

这样子我们就把原来的三个for循环优化成为了两个for循环 效率提高了不少

钱包问题三

我们给定一个数组 arr 数组里面的值表示任意一张面值的钞票 arr内部有相同值的钞票 我们认为值相同的钞票位置可以随意替换 (和题目一不同 题目一中每张钞票都是不同的 )

现在我们给定一个 val 值 请问有多少种组合方案可以让val值为0

这问题其实是题目二的变形

这里我提供一种思路将其转化为题目二 具体的代码大家可以自己尝试下

我们统计有多少种不同的钞票 并且将这些钞票添加到一个数组中

统计每个钞票的数目 再做一个数组

其中 第一个数组的用途和钱包问题二中的用途一样 而第二个数组则约束了每张钞票最多能取多少

之后按照钱包问题二的思路去做即可

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

牛客:FZ12 牛牛的顺时针遍历

FZ12 牛牛的顺时针遍历 文章目录 FZ12 牛牛的顺时针遍历题目描述题解思路题解代码 题目描述 题解思路 通过一个变量来记录当前方向&#xff0c;遍历矩阵&#xff0c;每次遍历一条边&#xff0c;将该边的信息加入到结果中 题解代码 func spiralOrder(matrix [][]int) []int {…

“Flex弹性布局、轮播图mock遍历数据和首页布局解析与实践“

目录 引言1. Flex弹性布局介绍及使用什么是Flex弹性布局&#xff1f;Flex容器与Flex项目Flex属性详解 2. 轮播图mock遍历数据简述轮播图的作用和意义处理mock数据的重要性使用Mock模拟数据遍历 3. 首页布局总结 引言 在现代网页开发中&#xff0c;灵活性和响应式布局是至关重要…

网络安全—小白自学笔记

1.网络安全是什么 网络安全可以基于攻击和防御视角来分类&#xff0c;我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术&#xff0c;而“蓝队”、“安全运营”、“安全运维”则研究防御技术。 2.网络安全市场 一、是市场需求量高&#xff1b; 二、则是发展相对成熟…

个人博客系统的总结

个人博客系统 1、项目背景&#xff1a; 个人博客系统的兴起和发展是与信息技术和互联网的迅猛发展密切相关的。随着互联网的普及和数字化时代的到来&#xff0c;越来越多的人开始使用互联网平台来表达自己的观点、分享知识和展示个人创作。个人博客系统作为一种在线的个人信息…

计算机网络-计算机网络体系结构-数据链路层

目录 *一、组帧 1.1字符计数法 1.2字符填充法 1.3零比特填充法 1.4违规编码 *二、差错控制 2.1检错编码 2.2.1奇偶校验码 2.2.2 CRC循环冗余码 2.2纠错编码-海明码 *三、流量控制和可靠传输机制 流量控制 停止-等待协议 ​编辑 后退n帧协议的滑动窗口(GBN) 选择…

Mac下通过nvm管理node

背景 本地有两个项目&#xff0c;老项目需要用到node 14&#xff0c;新项目需要用node 16&#xff0c;所以只能通过nvm来管理node了 卸载原始的node 我的node是通过官网的.pkg文件安装的&#xff0c;可以通过以下命令进行删除 sudo rm -rf /usr/local/{bin/{node,npm},lib/…

阿里云2023年双十一优惠活动整理

随着双十一的临近&#xff0c;阿里云也为大家准备了一系列优惠活动。作为国内知名的云服务提供商&#xff0c;阿里云在双十一期间推出了多种优惠政策和福利&#xff0c;让用户在享受优质云服务的同时&#xff0c;也能节省一些费用。本文将对阿里云双十一优惠活动进行详细整理&a…

项目经理每天,每周,每月的工作清单

很多不懂项目管理的伙伴问&#xff0c;项目经理每天每周每个月的工作是什么呢&#xff1f; 仿佛他们什么都管&#xff0c;但是又没有具体的产出&#xff0c;但是每天看他们比谁都忙&#xff0c;其实很简单&#xff0c;项目中的每个环节负责具体的事情&#xff0c;但是每个环节…

Nginx:动静分离(示意图+配置讲解)

示意图&#xff1a; 动静分离 动静分离是指将动态内容和静态内容分开处理的一种方式。通常&#xff0c;动态内容是指由服务器端处理的&#xff0c;例如动态生成的网页、数据库查询等。静态内容是指不需要经过服务器端处理的&#xff0c;例如图片、CSS、JavaScript文件等。通过…

excel管理接口测试用例

闲话休扯&#xff0c;上需求&#xff1a;自动读取、执行excel里面的接口测试用例&#xff0c;测试完成后&#xff0c;返回错误结果并发送邮件通知。 分析&#xff1a; 1、设计excel表格 2、读取excel表格 3、拼接url&#xff0c;发送请求 4、汇总错误结果、发送邮件 开始实现…

Tang Capital宣布收购纳斯达克上市公司Rain Oncology100%股权

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 猛兽财经获悉&#xff0c;纳斯达克上市公司Rain Oncology(Rain)宣布近期已收到Tang Capital Partners旗下的子公司Concentra Biosciences以每股1.25美元的现金收购要约。 这家临床阶段微型市值癌症治疗药物开发商的股价在消…

Apache atlas 元数据管理治理平台使用和架构

1、前言 Apache Atlas 是托管于 Apache 旗下的一款元数据管理和治理的产品&#xff0c;目前在大数据领域应用颇为广泛&#xff0c;可以很好的帮助企业管理数据资产&#xff0c;并对这些资产进行分类和治理&#xff0c;为数据分析&#xff0c;数据治理提供高质量的元数据信息。…

Ansible的playbook编写和运行示例介绍

目录 一.yaml语法格式 1.定义&#xff1a; 2.yaml支持几种数据类型 &#xff08;1&#xff09;纯量&#xff1a; &#xff08;2&#xff09;对象 &#xff08;3&#xff09;数组 3.playbook-yaml书写的注意事项 二.playbook编写和运行 1.单个简单playbook示例 &#…

前端开发工具vscode

一、下载安装 https://code.visualstudio.com/ 二、安装插件 三、使用 ①、创建一个空目录 ②、利用vscode工具打开该目录 ③、将该目录设置为工作区 在工作区中添加文件&#xff0c;还可以进行浏览器访问&#xff08;提前安装了Live Server插件&#xff09; 为工具…

如何让大模型自由使用外部知识与工具

本文将分享为什么以及如何使用外部的知识和工具来增强视觉或者语言模型。 全文目录&#xff1a; 1. 背景介绍 OREO-LM: 用知识图谱推理来增强语言模型 REVEAL: 用多个知识库检索来预训练视觉语言模型 AVIS: 让大模型用动态树决策来调用工具 技术交流群 建了技术交流群&a…

uniapp 运行到 app 报错 Cannot read property ‘nodeName‘ of null

uniapp 运行到某一个页面&#xff0c;报错&#xff0c;h5没有问题 Unhandled error during execution of scheduler flush. This is likely a Vue internals bug. Please open an issue at https://new-issue.vuejs.org/?repovuejs/coreat <GuiPagecustomHeadertruecustomF…

Java —— 运算符

目录 1. 什么是运算符 2. 算术运算符 2.1 基本四则运算符: 加减乘除模( - * / %) 2.2 增量运算符 - * %与 自增/自减运算符 -- 3. 关系运算符 4. 逻辑运算符 4.1 逻辑与 && 4.2 逻辑或|| 4.3 逻辑非 ! 4.4 短路求值 5. 位运算符 5.1 按位与 & 5.2 按位或 5.3 按位…

这应该是关于回归模型最全的总结了(附原理+代码)

本文将继续修炼回归模型算法&#xff0c;并总结了一些常用的除线性回归模型之外的模型&#xff0c;其中包括一些单模型及集成学习器。 保序回归、多项式回归、多输出回归、多输出K近邻回归、决策树回归、多输出决策树回归、AdaBoost回归、梯度提升决策树回归、人工神经网络、随…

System.exit()方法参数

说明文档&#xff1a;System (Java Platform SE 8 ) 终止当前正在运行的Java虚拟机。该参数用作状态代码&#xff1b;按照惯例&#xff0c;非零状态码表示异常终止。 此方法调用类Runtime中的exit方法。此方法从不正常返回。 调用System.exit&#xff08;n&#xff09;实际上等…

鸿蒙初体验

下载与安装DevEco Studio 在HarmonyOS应用开发学习之前&#xff0c;需要进行一些准备工作&#xff0c;首先需要完成开发工具DevEco Studio的下载与安装以及环境配置。 进入DevEco Studio下载官网&#xff0c;单击“立即下载”进入下载页面。 DevEco Studio提供了Windows版本和…