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

news2024/12/23 14:09:21

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

题目是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 case
        if (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 cur
        vector<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.coloradmin.cn/o/1098863.html

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

相关文章

到底什么才是真正的商业智能(BI)

随着人工智能、云计算、大数据、互联网、物联网等新一代信息化、数字化技术在各行各业内开始大规模的应用&#xff0c;社会上的数字化、信息化程度不断加深&#xff0c;而数据价值也在这样的刺激下成为了个人、机构、企业乃至国家的重要战略资源&#xff0c;成为了继土地、劳动…

win11 搭建Apache webdav 设置用户名密码 加密授权访问以及多个不同目录访问

Apache webdav 的搭建应该比较简单,但是搭建后还遇到了一些问题,也就是设置了访问用户名密码,咋就不生效呢,苦苦思索两日,终于发现了问题,本文就是分两个方面来编写 一、搭建 1.下载Apache 官网下载: https://www.apachehaus.com/cgi-bin/download.plx 2.下载后解压…

【ECharts】仪表盘指针自定义形状

这里我把指针改为扇形图&#xff0c;如下 主要是通过如下代码实现自定义形状的指针&#xff1a; var option {series: [{pointer: {icon: image://data:image/png;base64,iVBORw0KGgoAAAANSU...,},}]}; 完整代码如下&#xff1a; var chartDom document.getElementById(my…

今日多写一行注释,明日维护少掉一根头发

&#x1f451; 个人主页 &#x1f451; &#xff1a;&#x1f61c;&#x1f61c;&#x1f61c;Fish_Vast&#x1f61c;&#x1f61c;&#x1f61c; &#x1f41d; 个人格言 &#x1f41d; &#xff1a;&#x1f9d0;&#x1f9d0;&#x1f9d0;说到做到&#xff0c;言出必行&am…

C++11——右值引用

文章目录 1. 左值和右值1.1 什么是左值1.2 什么是右值 2. 左值引用和右值引用2.1 左值引用的使用场景2.2 右值引用的使用场景 3.移动语义4. 完美转发 1. 左值和右值 1.1 什么是左值 左值&#xff0c;不能根据名字来判断&#xff0c;即左边的就是左值&#xff0c;这个是错误的…

Linux系统使用AndroidStudio创建桌面快捷键

Linux系统使用AndroidStudio创建桌面快捷键 系统&#xff1a;deepin 20.7 Android studio 版本&#xff1a;Android Studio Iguana | 2023.2.1 Canary 1 下载免安装最新版本后&#xff0c;是直接在安装包中启动的&#xff0c;但是有点麻烦&#xff0c;就需要设置一个桌面快捷菜…

数据分析案例-基于snownlp模型的MatePad11产品用户评论情感分析(文末送书)

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

禁用和开启笔记本电脑的键盘功能,最快的方式

笔记本键盘通常较小&#xff0c;按键很不方便&#xff0c;当我们外接了键盘时就不需要再使用自带的键盘了&#xff0c;而且午睡的时候&#xff0c;总是担心碰到笔记本的键盘&#xff0c;可能会删掉我们的代码什么的&#xff0c;所以就想着怎么禁用掉&#xff0c;下面是操作步骤…

消失的人!消除视频中不需要的人物

视频拍摄中拍摄了不相干或者是不需要出现的人物&#xff0c;想要从视频中去除&#xff0c;应该如何操作呢&#xff1f;有什么快捷方法可以轻松扣除视频中的人物&#xff1f; 我们在视频剪辑的时候都有这样的烦恼吧&#xff1f;就是在一段视频素材里有多余的人物出现&#xff0…

女性用品经营商城小程序的作用是什么

女性悦己消费增强&#xff0c;围绕女性产生的商品&#xff0c;品牌多且样式足&#xff0c;消费者可以随时购买到&#xff0c;但随着线上互联网深入人们生活&#xff0c;电商近些年发展迅速&#xff0c;传统女性用品线下经销商或品牌在实际经营中面临着痛点。 线上卖货是各商家…

大数据 DataX 详细安装教程

目录 一、环境准备 二、安装部署 2.1 二进制安装 2.2 python 3 支持 三、Data X 初体验 3.1 配置示例 3.1.1. 生成配置模板 3.1.2 创建配置文件 3.1.3 运行 DataX 3.1.4 结果显示 3.2 动态传参 3.2.1. 动态传参的介绍 3.2.2. 动态传参的案例 3.3 迸发设置 …

快速学会Typora和Markdowm常用语法

什么是Markdown&#xff1f; Markdown 是一种轻量标记语言&#xff0c;和 Word、PDF、HMTL 格式一样&#xff0c;可以用作文章或者网页的格式。 和其它的格式相比&#xff0c;Markdown 具备以下优势&#xff1a; 易学易用&#xff0c;逼格满满&#xff1b; 格式简洁&#xff…

常用的原型工具有哪些?推荐这7款

原型图设计工具有很多优点。除了帮助设计师快速与客户达成协议&#xff0c;避免项目前景的冲突外&#xff0c;原型图设计工具还可以让客户看到正在创建的内容。如果需要更改&#xff0c;原型图设计工具也可以轻松完成。本文快速总结了7种原型图设计工具。无论你是专业设计师还是…

行情分析——加密货币市场大盘走势(10.16)

目前大饼再次止稳&#xff0c;并开始向上攀升&#xff0c;目前MACD来看也是进入了多头趋势。重新调整了蓝色上涨趋势线&#xff0c;目前来看这次的低点并没有跌破上一个低点&#xff0c;可以认为是上涨的中继。注意白天的下跌回调。 以太目前也是走了四连阳线&#xff0c;而MAC…

网络安全—小白自学笔记

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

你知道跨境商城源码如何为商家节省成本和时间吗

跨境电商行业迅速发展&#xff0c;商家如何利用跨境商城源码实现成本和时间节省 在全球经济一体化的背景下&#xff0c;跨境电商行业蓬勃发展&#xff0c;为商家提供了全球范围的市场机会。然而&#xff0c;面临的挑战也日益增多&#xff0c;比如高昂的运营成本和繁琐的流程&am…

嵌入式面试经典30问

嵌入式面试经典30问 很多同学说很害怕面试&#xff0c;看见面试官会露怯&#xff0c;怕自己的知识体系不完整&#xff0c;怕面试官考的问题回答不上了&#xff0c;所以今天为大家准备了嵌入式工程师面试经常遇到的30个经典问题&#xff0c;希望可以帮助大家提前准备&#xff0…

weapp-tailwindcss for uni-app 样式条件编译语法插件

weapp-tailwindcss for uni-app 样式条件编译语法插件 版本需求 2.10.0 weapp-tailwindcss for uni-app 样式条件编译语法插件 这是什么玩意?如何使用 tailwind.config.js 注册postcss 插件注册 uni-app vite vue3uni-app vue2 配置完成 配置项 这是什么玩意? 在 uni-app …

GeoServer源码运行(数据目录+数据库)

1、源码下载 下载地址:https://github.com/geoserver/geoserver/tree/2.23.2 图 2选择版本下载 2、启动配置 图 3主程序启动类配置 GeoServer主程序的启动类为web->app[gs-web-app]模块下test目录下“org.geoserve