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

news2024/9/27 9:28:43

暴力递归到动态规划(一)

    • 斐波那契数列的动态规划
    • 机器人走路
      • 初级递归
      • 初级动态规划
      • 动态规划
    • 先后选牌问题
      • 初级递归
      • 初级动态规划
      • 动态规划

我们可以一句话总结下动态规划

动态规划本质是一种以空间换时间的行为 如果你发现有重复调用的过程 在经过一次之后把结果记下来 下次调用的时候直接用 这就是动态规划

斐波那契数列的动态规划

一般来说我们可以使用递归来解决斐波那契数列问题 代码如下

int fib(int n)    
{    
  if (n <= 0)    
  {    
    cerr << "error" << endl;    
  }    
    
  if (n <= 2)    
  {    
    return 1;    
  }    
    
  return fib(n-1) + fib(n-2);    
}    

当然 这种方式会产生大量的重复计算 所以说我们可以保存上个和上上个的计算值来进行动态规划

int dpfib(int n)    
{    
  if (n <= 2)    
  {    
    return 1;    
  }    
    
  int i = 1;    
  int j = 1;    
  int k = 0;    
    
  while (n-2)    
  {    
    k = i + j;    
    i = j;    
    j = k;     
    n--;    
  }    
    
  return k;    
}     

这样子写代码就能避免大量的重复计算了

机器人走路

初级递归

假设现在有1~N个位置

在这里插入图片描述

有一个小机器人 现在在START位置

在这里插入图片描述
它现在要去aim位置 (aim为1~N上的随机一点) 能走K步 (K >= 0)

每次只能走一步 不能越界 不能停止 现在请问有多少种方式能走走到

现在假设 S = 2 N = 5 AIM = 4 K = 6

我们一开始写出的递归方程如下

int process1(int cur , int rest , int aim , int k)  

参数含义如下

  • cur 当前位置
  • rest 剩余步数
  • aim 目标地点
  • k 能走的步数

base case为

  • 如果剩余步数为0 则判断cur是否为aim地点

否则我们执行递归继续往下走

int process1(int cur , int rest , int aim , int n)
{                             
  if (rest == 0)
  {
    return cur == aim ? 1 : 0;
  }
                                           
  if (cur == 1)
  {
    return process1(2 , rest -1 , aim , n);
  }
                                            
  if (cur == n)
  {
    return process1(n-1 , rest-1 , aim , n);
  }                                      
 
  return process1(cur-1 , rest-1 , aim , n)
    + process1(cur+1 , rest-1 , aim , n);                                                                                       
}  

初级动态规划

现在我们要进行进一步的动态规划

我们想想看在递归函数中有真正决定的是哪两个参数

我们每次传递参数的时候 aimn 是不变的

其实每次变化的就是 currest

在这里插入图片描述

我们可以将该函数往下推演 我们会发现会出现两个相同的结果

如果继续往下展开的话则肯定会有重复的部分 所以说我们最好能将这些函数的结果记录下来 避免重复计算

我们选择使用一个二维数组存储每个函数的计算结果

 vector<vector<int>> dp(n + 1 , vector<int>(rest + 1));    
  for (int i = 0 ; i < n + 1 ; i++ )    
  {    
    for (int j = 0; j < rest + 1 ; j++)                                                                                         
    {    
      dp[i][j] = -1;    
    }    
  }    

这个二维数组 i j 分别标识当前位置和剩余步数

该数组的值表示在当前位置下走剩余步数能走到目的地有多少种解法

我们首先全部初始化为-1

int _process2(int cur , int rest , int aim , int n , vector<vector<int>>& dp)
{
  if (dp[cur][rest] != -1)
  {
    return dp[cur][rest];
  }

  int ans = 0;
  if (rest == 0)
  {
    ans = cur == aim ? 1 : 0;
  }
  else if (cur == 1)
  {
    ans = _process2(2 , rest-1 , aim , n , dp);
  }
  else if (cur == n)
  {
    ans = _process2(n-1 , rest-1 , aim , n , dp);
  }                                                                                                                             
  else 
  {
    ans = _process2(cur -1  , rest -1 , aim , n , dp ) 
      + _process2(cur +1 , rest -1 , aim , n , dp);    
  }    

  dp[cur][rest] = ans;
  return ans;
 }

之后在我们的函数中 如果数组中有结果 我们就直接返回 如果没有结果我们就将结果记录在数组中后返回 也能得到一样的结果

动态规划

我们以cur为横坐标 rest为纵坐标画一个图 并且将cur为0的时候值填入图中
在这里插入图片描述
当cur为1的时候 我们回顾下我们的代码 我们会发现 此时该格上的数字只依赖于 dp[2][rest-1]

当cur为n的时候 我们回顾下之前的带啊吗 我们会发现 此时该格上的数字只依赖于 dp[n-1][rest-1]

当cur介于两者之间的时候 此时该格上的数字依赖于两种路径

dp[cur-1][rest-1] + dp[cur+1][rest-1]

如下图
在这里插入图片描述

那么我们既然有了第一列的数字 我们就可以推出整个dp数组的数值

从而我们就能得出 当前为start 还有k步要走的时候 我们有几种路径

代码表示如下

int process3(int cur , int rest , int aim , int n)
{
  vector<vector<int>> dp(n + 1 , vector<int>(rest + 1));    
  for (int i = 0 ; i < n + 1 ; i++ )    
  {    
    for (int j = 0; j < rest + 1 ; j++)    
    {    
      dp[i][j] = 0;    
    }    
  }    
    
  // row 1    
  dp[aim][0] = 1;    
    
    
  for (int r = 1 ; r <= rest ; r++)    
  {    
    dp[1][r] = dp[2][r-1];    
    
    for (int c = 2 ; c < n ; c++)                                                                                               
    {    
      dp[c][r] = dp[c-1][r-1] + dp[c+1][r-1];    
    }    
    
    dp[n][r] = dp[n-1][r-1];    
  }    
    
    
  return dp[cur][rest];   
} 

这就是完整的解决动态规划问题的步骤

先后选牌问题

初级递归

假设现在给你一个数组 长度为N (N > 0) 数组内部储存着int类型的值 大小为(1~100)

现在两个人先后选数字 有如下规定

  • 只能选择最边界的数字
  • 如果这个数字被选择了 则从数组中移除

那么我们其实可以写出先选和后选两个函数

一开始我们写出的递归函数如下

int f(vector<int>& arr , int L , int R)  
int g(vector<int>& arr , int L , int R)

这里两个函数的意义分别是

  • f优先选择的最大值
  • g后手选择的最大值

参数的含义是

  • arr 数组
  • L 左边界
  • R 右边界

代码表示如下

int f(vector<int>& arr , int L , int R)    
{                
  if (L == R)                                                                                                                   
  {                   
    return arr[L];    
  }                                         
                                            
  int p1 = arr[L] + g(arr , L + 1 , R );    
  int p2 = arr[R] + g(arr , L , R - 1);    
                          
  return max(p1 , p2);    
}     
                                           
                                             
int g(vector<int>& arr , int L , int R)    
{                  
  if (L == R)    
  {                
    return 0;    
  }                               
                                    
  int p1 = f(arr , L + 1 , R);
  int p2 = f(arr , L , R -1);

  return min(p1 , p2);
}

这里解释下为什么g函数中要取最小值

因为先手的人可能会拿走L或者R 给我们造成p1 或者 p2两种结果

因为先手的人要赢 所以说只可能会给我们最差的一种结果 所以一定会是较小的那个值

初级动态规划

这里变化的参数实际上就只有左边界和右边界

我们就可以围绕着这两个边界来建表

在这里插入图片描述

如果按照函数的依赖关系展开 我们很快就会发现重复项

所以说我们要围绕着重复项建表来达到动态规划的效果

而由于这里有两个函数 f 和 g 所以说 我们要建立两张表

我们以为L为横坐标 R为纵坐标建立两张表

L和R的范围是1 ~ R+1

代码表示如下

int _f2(vector<int>& arr , int L , int R , vector<vector<int>>& fmap , vector<vector<int>>& gmap)    
{                         
  if (fmap[L][R] != 0)    
  {    
    return fmap[L][R];                                                                                                          
  }    
                  
  int ans = 0;    
    
  if ( L == R)    
  {    
    ans = arr[L];    
  }    
  else     
  {    
    int p1 = arr[L] + _g2(arr , L+1 , R , fmap , gmap);    
    int p2 = arr[R] + _g2(arr , L , R-1 , fmap , gmap);    
    ans = max(p1 , p2);    
  }    
    
  fmap[L][R] = ans;    
  return ans;    
    
} 
int _g2(vector<int>& arr , int L , int R , vector<vector<int>>& fmap , vector<vector<int>>& gmap)
{                     
  if (gmap[L][R] != 0)
  {
    return gmap[L][R];
  }
             
  int ans = 0;
            
  if (L == R)
  {    
    ans = 0;
  }                                            
  else                                          
  {                    
    int p1 = _f2(arr , L , R -1 , fmap , gmap);
    int p2 = _f2(arr , L + 1 , R , fmap , gmap);
    ans = min(p1 , p2);
  }          
                                                                                                                                
  gmap[L][R] = ans;
  return ans;
}

动态规划

我们以L为横坐标 R为纵坐标画一个gmap图 并且将L == R的时候的值填入图中

在这里插入图片描述

我们可以发现该图的左下角我们是不需要的因为L不可能大于R

那么我们的gmap上右上角的随机一点是依赖于什么呢?

回归到我们最初的递归方程中

_f2(arr , L , R -1 , fmap , gmap);
_f2(arr , L + 1 , R , fmap , gmap);

我们可以发现是依赖于fmap的 L R-1 以及 L+1 R
在这里插入图片描述

如果映射到fmap中 我们可以发现刚好是斜对角线上的两点 既然我们现在知道了斜对角线上的值 我们现在就可以开始填写这两张map表了

代码表示如下

int f3(vector<int>& arr , int L , int R)
{
  
  vector<vector<int>> fmap( R + 1, vector<int>(R+1));
  for (int i = 0 ; i < R + 1 ; i++)
  {
    for (int j = 0; j < R + 1 ; j++)
    {
      fmap[i][j] = 0;
      if (i == j)
      {
        fmap[i][j] = arr[i];
      }
    }
  }

  vector<vector<int>> gmap( R+1 , vector<int>(R+1));
  for (int i = 0 ; i < R + 1 ; i++)
  {
    for (int j = 0; j < R + 1 ; j++)
    {
      gmap[i][j] = 0;
    }
  }                                                                                                                             

  for (int startcol = 1 ; startcol < R + 1; startcol++ )
  {
     int col = startcol;
     int row = 0;
     
     while (col < R + 1)
     {
       fmap[row][col] = max(gmap[row][col-1] + arr[row] , gmap[row+1][col] + arr[col]);
       gmap[row][col] = min(fmap[row][col-1] , fmap[row+1][col]);

       row++;
       col++;
     }
  
  }

  return fmap[L][R];
}

其实最关键的代码就是这两行

  fmap[row][col] = max(gmap[row][col-1] + arr[col] , gmap[row+1][col] + arr[row]);
  gmap[row][col] = min(fmap[row][col-1] , fmap[row+1][col]);

我们可以发现 这其实就是将原来代码中的函数

    int p1 = arr[L] + _g2(arr , L+1 , R , fmap , gmap);    
    int p2 = arr[R] + _g2(arr , L , R-1 , fmap , gmap);    
    ans = max(p1 , p2);    

变成了表中的数字相加

这就是动态规划的一般套路

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

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

相关文章

jmeter怎样的脚本设计才能降低资源使用

官网地址&#xff1a;Apache JMeter - Users Manual: Best Practices 1、用好断言 频繁的使用断言会加大资源的消耗&#xff0c;尽可能减少断言的使用&#xff0c;或者在使用的过程中断言数据文本尽量精简 2、使用命令执行 启动的时候就提示我们在执行压测的时候应该用命令执…

自动化测试框架有哪些?怎么选?今天我来告诉你

前言 随着软件开发过程中的复杂度不断提高&#xff0c;自动化测试成为了一个必要的手段。Python作为一种灵活易用的编程语言&#xff0c;已经成为自动化测试领域的一种主流工具。Python自动化测试框架可以使得我们更加方便地进行测试脚本的编写和执行&#xff0c;同时也可以提…

【力扣】2. 两数相加

题目描述 给你两个 非空 的链表&#xff0c;表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的&#xff0c;并且每个节点只能存储 一位 数字。 请你将两个数相加&#xff0c;并以相同形式返回一个表示和的链表。 你可以假设除了数字 0 之外&#xff0c;这两个数都…

Centos指令合集

2023-10-09 防火墙 开启 systemctl start firewalld自启动 systemctl enable firewalld.service关闭 systemctl stop firewalld禁用 systemctl disable firewalld.service查看状态 systemctl status firewalld

OpenCV4(C++)—— 仿射变换、透射变换和极坐标变换

文章目录 一、仿射变换1. getRotationMatrix2D()2. warpAffine() 二、透射变换三、极坐标变换 一、仿射变换 在OpenCV中没有专门用于图像旋转的函数&#xff0c;而是通过图像的仿射变换实现图像的旋转。实现图像的旋转首先需要确定旋转角度和旋转中心&#xff0c;之后确定旋转…

Windows10打开应用总是会弹出提示窗口的解决方法

用户们在Windows10电脑中打开应用程序&#xff0c;遇到了总是会弹出提示窗口的烦人问题。这样的情况会干扰到用户的正常操作&#xff0c;给用户带来不好的操作体验&#xff0c;接下来小编给大家详细介绍关闭这个提示窗口的方法&#xff0c;让大家可以在Windows10电脑中舒心操作…

Java Agent初探

1&#xff1a;Java Agent简介 Java Agent 这个技术出现在 JDK1.5 之后&#xff0c;对于大多数人来说都比较陌生&#xff0c;但是多多少少又接触过&#xff0c;实际上&#xff0c;我们平时用的很多工具&#xff0c;都是基于 Java Agent 实现的&#xff0c;例如常见的热部署 JRe…

电脑中的opencl.dll丢失怎么办,三步解决opencl.dll丢失

最近有不少用户都遇到了opencl.dll丢失的情况&#xff0c;其实解决opencl.dll丢失的办法很简单&#xff0c;今天就来教大家如何用三步解决opencl.dll丢失的问题。 一.了解opencl.dll opencl.dll是OpenCL的动态链接库文件。OpenCL&#xff08;Open Computing Language&#xff…

上班第一天同事让我下载个小乌龟,我就去百度小乌龟。。。。

记得那会儿是刚毕业&#xff0c;去上班第一天&#xff0c;管我的那个上级说让我下载个小乌龟&#xff0c;等下把代码拉一下&#xff0c;我那是一脸懵逼啊&#xff0c;我在学校只学过git啊&#xff0c;然后开始磨磨蹭蹭吭吭哧哧的不知所措&#xff0c;之后我想也许百度能救我&am…

华为云云耀云服务器L实例评测 | 实例使用教学之高级使用:配置 Git SSH Key 进行自动识别拉代码

华为云云耀云服务器L实例评测 &#xff5c; 实例使用教学之高级使用&#xff1a;配置 Git SSH Key 进行自动识别拉代码 介绍华为云云耀云服务器 华为云云耀云服务器 &#xff08;目前已经全新升级为 华为云云耀云服务器L实例&#xff09; 华为云云耀云服务器是什么华为云云耀云…

05-进程控制

1. 学习目标 了解进程相关的概念掌握fork/getpid/getppid函数的使用熟练掌握ps/kill命令的使用熟练掌握execl/execlp函数的使用说出什么是孤儿进程什么是僵尸进程熟练掌握wait函数的使用熟练掌握waitpid函数的使用 2 进程相关概念 2.1 程序和进程 程序&#xff0c;是指编译好…

mysql数据库root密码忘记了,这里有一个简单的方法可以解决

mysql安装久了&#xff0c;就容易忘记root密码&#xff0c;那么下面这个找回密码的方法将解决你的问题&#xff1a; 特别注意事项&#xff1a; 本方法只适合mysql数据库密码遗忘&#xff08;忘记了&#xff09; 这个解决方案的前提是你的电脑里安装了navicat&#xff08;其他…

阿桂天山的技术小结:Sqlalchemy+pyodbc连接MSSQL server测试

话不多说,有图有源码 1)确保本机安装了sql server对应的odbc驱动 在控制面板的管理工具中可以查&#xff1a;数据源(ODBC) 我这里已经安装了,如果没有安装可以自行下载安装 2)连接MsSql Server代码 # -*- coding: utf-8 -*- __author__ "阿桂天山"#----------判…

【位图+布隆过滤器】

目录 一、位图1.1位图的概念1.2位图的实现 二、布隆过滤器2.1布隆过滤器的概念2.2布隆过滤器的实现 三、位图的扩展--找只出现一次的数 一、位图 1.1位图的概念 所谓位图&#xff0c;就是用每一位来存放某种状态&#xff0c;适用于海量数据&#xff0c;数据无重复的场景。通常…

IDEA 配置 云服务器远程部署

目录 参考资料远程部署与远程开发远程连接配置配置成功&#xff1a;同步文件自动更新文件配置自动更新文件参数调整正确运行问题1&#xff1a;运行mvn spring-boot:run之后一直卡在第一条下载问题2&#xff1a;运行成功后访问不到问题3&#xff1a;无法配置远程开发 参考资料 …

Vmware下载安装以及创建虚拟机

虚拟机有很多种&#xff0c;常见的有VMware Workstation、VirtualBox等。这里以VMware Workstation为例&#xff0c;可在官网下载并安装。 目录 一、下载 二、安装 三、创建虚拟机 四、Ubuntu安装 下载ISO镜像 Ubuntu 使用ISO镜像 一、下载 第一步那就是要下载一个工具&…

基于Java的在线问卷调查系统的设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技…

PTA 7-2 彩虹瓶(单调栈)

题目 彩虹瓶的制作过程&#xff08;并不&#xff09;是这样的&#xff1a;先把一大批空瓶铺放在装填场地上&#xff0c;然后按照一定的顺序将每种颜色的小球均匀撒到这批瓶子里。 假设彩虹瓶里要按顺序装 N 种颜色的小球&#xff08;不妨将顺序就编号为 1 到 N&#xff09;。…

一文3000字从0到1使用pytest-xdist实现分布式APP自动化测试

目录 01、分布式测试的原理 02、测试项目 03、环境准备 04、搭建步骤 05、分布式执行 06、测试报告 不知道大家有没有遇到这样一种情况&#xff0c;实际工作中&#xff0c;app自动化测试的用例可能是成百上千条的&#xff0c;如果放在一台机器上跑&#xff0c;消耗的时间…

数据结构--算法、数据结构的基本概念

&#x1f4d5;参考&#xff1a;王道 一、算法的基本概念 1.程序数据结构算法 2.算法的特性 &#xff08;1&#xff09;有穷性 执行有穷步之后结束&#xff0c;且每一步都可在有穷时间内完成。 &#xff08;2&#xff09;确定性 &#xff08;3&#xff09;可行性 可通过已经实…