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

news/2024/5/3 20:42:35/文章来源:https://blog.csdn.net/meihaoshy/article/details/133771412

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

    • 最长公共子序列
      • 递归版本
      • 动态规划
    • 最长回文串子序列
      • 方法一
      • 方法二
      • 递归版本
      • 动态规划
    • 象棋问题
      • 递归版本
      • 动态规划
    • 咖啡机问题
      • 递归版本
      • 动态规划

最长公共子序列

这是leetcode上的一道原题 题目连接如下

最长公共子序列

题目描述如下

给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。

一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。

  • 例如,“ace” 是 “abcde” 的子序列,但 “aec” 不是 “abcde” 的子序列。

两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。

递归版本

还是一样 我们首先来设计一个函数 函数原型如下

int process(string& str1 , string& str2 , int i , int j)

这个递归函数的含义是 给你两个字符串 s1 和 s2 再给你它们的一个最大下标 现在要求这个函数返回它们公共子序列的最大值

参数表示如下

  • int i : 表示一个字符串str1中的下标
  • int j : 表示一个字符串str2中的下标

还是一样 我们首先想base case

  • 假如i的下标为0 j的下标也为0 此时我们就可以直接返回一个确定的值

代码表示如下

  // base case     if (i == 0 && j == 0)    {    return str1[i] == str2[j] ? 1 : 0;    }  

此时我们排除了i 和 j都为0的情况 剩下了三种情况

  • i j 其中一个为0 (两种)
  • i j都不为0

当i j都不为0时候 我们还要讨论 i j 是否为公共子序列的下标也是分为三种情况

  • i可能是 j不是
  • j可能是 i不是
  • i j都是

之后我们分别将代码全部写好就可以了

 if (i == 0){if (str1[i] == str2[j]){return 1;}else {return process(str1 , str2 , i , j-1);}}else if (j == 0){if (str1[i] == str2[j]){return 1;}else {                                                                                                                           return process(str1 , str2 , i - 1 , j);    }}else  {// j != 0;// i != 0;// possible i  ... jint p1 = process(str1 , str2 , i - 1 , j);int p2 = process(str1 , str2 , i , j - 1);int p3 = str1[i] == str2[j] ? 1 + process(str1 , str2 , i -1 , j -1) : 0 ;return max(p1 , max (p2 , p3));}
}

动态规划

我们观察原递归函数

process(string& str1 , string& str2 , int i , int j)

我们发现变化的值只有 i 和 j

于是我们可以利用i j 做出一张dp表

还是一样 我们首先来看base case

  // base case     if (i == 0 && j == 0)    {    return str1[i] == str2[j] ? 1 : 0;    }  

于是我们就可以把i == 0 并且 j ==0 的这些位置值填好

dp[0][0] = str1[0] == str2[0] ? 1 : 0;

之后根据 i == 0 j ==0 这两个分支继续动规

  for (int j = 1 ; j < static_cast<int>(str2.size()) ; j++){                                                              dp[0][j] = str1[0] == str2[j] ?  1 : dp[0][j-1];             }                                         for (int i = 1 ; i < static_cast<int>(str1.size()) ; i++){                                                        dp[i][0] = str1[i] == str2[0] ? 1 : dp[i-1][0];}

递归的最后一部分依赖三个位置

  else  {// j != 0;// i != 0;// possible i  ... jint p1 = process(str1 , str2 , i - 1 , j);int p2 = process(str1 , str2 , i , j - 1);int p3 = str1[i] == str2[j] ? 1 + process(str1 , str2 , i -1 , j -1) : 0 ;return max(p1 , max (p2 , p3));}

我们只需要再递归表中依次填写即可 代码表示如下

int process1(string& str1, string& str2, vector<vector<int>>& dp)    
{    dp[0][0] = str1[0] == str2[0] ? 1 : 0;    for (int j = 1 ; j < static_cast<int>(str2.size()) ; j++)    {    dp[0][j] = str1[0] == str2[j] ?  1 : dp[0][j-1];    }    for (int i = 1 ; i < static_cast<int>(str1.size()) ; i++)    {    dp[i][0] = str1[i] == str2[0] ? 1 : dp[i-1][0];    }    for (int i = 1 ; i < static_cast<int>(str1.size()) ; i++)    {    for (int j = 1 ; j < static_cast<int>(str2.size()) ; j++)    {    int p1 = dp[i-1][j];    int p2 = dp[i][j-1];    int p3 = str1[i] == str2[j] ? 1 + dp[i-1][j-1] : 0;    dp[i][j] = max(p1 , max(p2 , p3));                                                                                        }    }    return dp[str1.size() - 1][str2.size() - 1];    
}

最长回文串子序列

方法一

做这道题目我们其实可以复用下上面的最长公共子序列的代码来做

我们可以将字符串逆序一下创造出一个新的字符串

再找出这两个字符串的最长公共子序列 我们找出来的最长公共子序列就是回文子序列 (其实我们可以想想两个指针从一个字符串的两端开始查找)

方法二

递归版本

我们写的递归函数如下

int process(string& str , int L , int R)  

它的含义是 我们给定一个字符串str 返回给这个字符串从L到R位置上的最大回文子串

参数含义如下

  • str 我们需要知道回文子串长度的字符串
  • L 我们需要知道回文子串长度的起始位置
  • R 我们需要知道回文子串长度的终止位置

所有的递归函数都一样 我们首先来想base case

这道题目中变化的参数其实就只有L 和 R 所以说我们只需要考虑L和R的base case

如果L和R相等 如果L和R只相差1

  if (L == R)    {              return 1;    }              if (L == R - 1)    {                  return str[L] == str[R] ? 2 : 1;    }      

之后我们来考虑下普遍的可能性

  • 如果L 和 R就是回文子序列的一部分
  • 如果L可能是回文子序列的一部分 R不是
  • 如果L不是回文子序列的一部分 R有可能是

我们按照上面的可能性分析写出下面的代码 之后返回最大值即可

  int p1 = process(str , L + 1 , R);    int p2 = process(str , L , R - 1);int p3 = str[L] == str[R] ? 2 + process(str , L + 1, R - 1) : 0;                                                              return max(max(p1 , p2) , p3);

动态规划

我们注意到原递归函数中 可变参数只有L 和 R 所以说我们只需要围绕着L 和 R建立一张二维表就可以

当然 在一般情况下 L是一定小于等于R的 所以说L大于R的区域我们不考虑

我们首先来看base case

  if (L == R)    {              return 1;    }              if (L == R - 1)    {                  return str[L] == str[R] ? 2 : 1;    }   

围绕着这个base case 我们就可以填写两个对角线的内容

    for (int L = 0; L < str.size(); L++){for(int R = L; R < str.size(); R++){if (L == R){dp[L][R] = 0;}if (L == R-1){dp[L][R-1] = str[L] == str[R] ? 2 : 1;}}                                                                                                                         }

接下来我们看一个格子普遍依赖哪些格子

  int p1 = process(str , L + 1 , R);    int p2 = process(str , L , R - 1);int p3 = str[L] == str[R] ? 2 + process(str , L + 1, R - 1) : 0;          

从上面的代码我们可以看到 分别依赖于

L+1 R  
L , R-1
L+1 , R-1

从图上来分析 黑色的格子依赖于三个红色格子

在这里插入图片描述

于是我们就可以沿着对角线来不断的填写数字

横行一直从0开始 纵列一直在变化 所以我们用列来遍历

最后返回dp[0][str.size()-1]即可

  int process1(string& str ,  vector<vector<int>>& dp){for (int L = 0; L < str.size(); L++){for(int R = 0; R < str.size(); R++){if (L == R){dp[L][R] = 1;}if (L == R-1){dp[L][R] = str[L] == str[R] ? 2 : 1;}}}                                             for (int startR = 2; startR < str.size(); startR++){int L = 0;int R = startR;while (R < str.size()){int p1 = dp[L+1][R];int p2 = dp[L][R-1];int p3 = str[L] == str[R] ? 2 + dp[L+1][R-1] : 0;dp[L][R] = max(p1 , max(p2 , p3));L++;R++;}}return dp[0][str.size()-1];}

象棋问题

递归版本

现在给你一个横长为10 纵长为9的棋盘 给你三个参数 x y z

现在一个马从(0 , 0)位置开始运动

提问 有多少种方法使用K步到指定位置 (指定位置坐标随机给出 且一定在棋盘上)

首先我们可以想出这么一个函数

int process(int x , int y , int rest , int a , int b)   

它象棋目前在 x y位置 还剩下 rest步 目的地是到 a b位置

让你返回一个最多的路数

我们首先来想base case

  • 首先肯定是剩余步数为0 我们要开始判断是否跳到目的地了
  • 其次我们还要判断是否越界 如果越界我们直接返回0即可

代码表示如下

    if (x < 0 || x > 9 || y < 0 || y > 8){return 0;}if (rest == 0){return (x == a && y ==b) ? 1 : 0;}

接下来我们开始讨论普遍情况 其实就是把马的各个位置跳一遍

  int ways = process(x-2 , y+1 , rest-1 , a , b);    ways += process(x-1 , y+2 , rest-1 , a , b);    ways += process(x+1 , y+2 , rest-1 , a , b);    ways += process(x+2 , y+1 , rest-1 , a , b);    ways += process(x-2 , y-1 , rest-1 , a, b);    ways += process(x-1 , y-2 , rest-1 , a , b);    ways += process(x+1 , y-2 , rest-1 , a, b);                                                                                     ways += process(x+2 , y-1 , rest-1 , a ,b);    

其实这样子我们的代码就完成了 总体代码如下

int process(int x , int y , int rest , int a , int b)
{if (x < 0 || x > 9 || y < 0 || y > 8){return 0;}if (rest == 0)    {    return (x == a && y ==b) ? 1 : 0;    }    int ways = process(x-2 , y+1 , rest-1 , a , b);    ways += process(x-1 , y+2 , rest-1 , a , b);    ways += process(x+1 , y+2 , rest-1 , a , b);    ways += process(x+2 , y+1 , rest-1 , a , b);    ways += process(x-2 , y-1 , rest-1 , a, b);    ways += process(x-1 , y-2 , rest-1 , a , b);    ways += process(x+1 , y-2 , rest-1 , a, b);                                                                                     ways += process(x+2 , y-1 , rest-1 , a ,b);    return ways;    
}    

动态规划

我们对于原递归函数进行观察 可以得知

int process(int x , int y , int rest , int a , int b)

原函数中 变化的参数只有 x y 和rest 于是乎我们可以建立一个三维的数组

x的范围是0 ~ 9 y的范围是0 ~ 8 而rest的范围则是根据我们步数来决定的 0~K

所以说此时我们以X为横坐标 Y为纵坐标 REST为竖坐标

vector<vector<vector<int>>> dp(10 , vector<vector<int>>(9 , vector<int>(8 , 0))); 

我们首先看base case分析下

    if (x < 0 || x > 9 || y < 0 || y > 8){return 0;}

如果有越界的地方 我们直接返回0即可

   if (rest == 0){return (x == a && y ==b) ? 1 : 0;}

在z轴为0的时候 我们只需要将a b 0坐标标记为1即可

nt process1(int k , int a , int b , vector<vector<vector<int>>>& dp)
{                             dp[a][b][0] = 1;               for (int z = 1; z <= k; z++)                 {                                          for (int x = 0; x < 10; x++)             {                                          for (int y = 0; y < 9; y++)            {                                      int ways = pickdp(x-2 , y+1 , z-1, dp);ways += pickdp(x-1 , y+2 , z-1 , dp);ways += pickdp(x+1 , y+2 , z-1 , dp);ways += pickdp(x+2 , y+1 , z-1 , dp);ways += pickdp(x-2 , y-1 , z-1 , dp);ways += pickdp(x-1 , y-2 , z-1 , dp);ways += pickdp(x+1 , y-2 , z-1 , dp);                                                                                         ways += pickdp(x+2 , y-1 , z-1 , dp);dp[x][y][z] = ways;}}                }return dp[0][0][k];
}

咖啡机问题

给你一个数组arr arr[i]表示第i号咖啡机泡一杯咖啡德时间

给定一个正数N 表示第N个人等着咖啡机泡咖啡 每台咖啡机只能轮流泡咖啡

只有一台洗咖啡机 一次只能洗一个被子 时间耗费a 洗完才能洗下一杯

当然 每个咖啡杯也能自己挥发干净 挥发干净的时间为b 咖啡机可以并行的挥发

假设所有人拿到咖啡之后立刻喝干净

返回从开始等待到所有咖啡机变干净的最短时间


我们首先来分析下题目

这里其实是两个问题

  • 问题一 每个人喝咖啡喝完的时间是多少
  • 问题二 每个人洗完的时间是多少

对于问题一 我们可以使用一个小根堆来做

我们定义一个机器类 里面有两个成员函数

机器的开始时间和机器的使用时间 我们使用它们相加之和来作为小根堆排序的依据

之后我们就能得到每个人喝完咖啡的最优解了

class Machine     
{    public:    int _starttime;    int _worktime;    public:    int getsum() const    {    return _starttime + _worktime;    }    public:    Machine() = default;    Machine(int st , int wt)    :_starttime(st)    ,_worktime(wt)    {}    bool operator()(const Machine& obj1 , const Machine& obj2)    {    return obj1.getsum() > obj2.getsum();    }    
};   
vector<int>  process(vector<int>& arr , int num) 
{vector<int> ans;priority_queue<Machine , vector<Machine> , Machine> heap;for (auto x : arr)                                                                                                                  {heap.push(Machine(0 , x));}for (int i = 0; i < num; i++){Machine cur  = heap.top();heap.pop();ans.push_back(cur.getsum());cur._starttime += cur._worktime;heap.push(Machine(cur._starttime , cur._worktime));}return ans;
}

递归版本

我们在写递归版本的时候首先要想到递归函数的含义

它的含义是返回一个所有咖啡杯都被洗完的最小值

之后我们可以想base case 当什么样的时候 该函数无法递归了

最后列出所有可能性即可

int process(vector<int>& end , int index , int a , int b , int avltime)
{if (index == static_cast<int>(end.size())){return 0;}    // possible 1     int p1 = max(a + end[index] , process(end , index+1 , a , b , avltime));    // possible 2    int p2 = max(b + end[index], process(end , index+1 , a , b , avltime + b));                                                         return min(p1 , p2);    
}

动态规划

这个问题的动态规划涉及到一个大小问题

因为我们无法确定avltime使用到的时间 所以这里有两种解决方案

  • 将它开辟的足够大
  • 根据最大值计算 (假设所有人都用咖啡机洗)
int dpprocess(vector<int>& end , int a , int b , vector<vector<int>> dp)
{// dp[N][...] = 0;for (int indexdp = static_cast<int>(end.size()) - 1; indexdp >= 0 ; indexdp--){for (int freetime = 0; freetime <= 10000 ; freetime++){int p1 = max(a + end[indexdp] , dp[indexdp+1][freetime]);int p2 = max(b + end[indexdp] , dp[indexdp+1][freetime+b]);dp[indexdp][freetime] = min(p1 , p2);}}return dp[0][0];
}

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

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

相关文章

NodeMCU ESP8266 基于Arduino IDE的串口图形化调试教程(超详细)

NodeMCU ESP8266 基于Arduino IDE的串口图形化调试教程 文章目录 NodeMCU ESP8266 基于Arduino IDE的串口图形化调试教程前言Serial Plotter测试前期准备打开工具方法 1方法 2 测试代码 总结 前言 在嵌入式的开发过程中&#xff0c;我们经常会采集一些传感器的数据&#xff0c…

iCloud涨价不用慌!学会使用群晖生态将本地SSD“上云”

文章目录 前言本教程解决的问题是&#xff1a;按照本教程方法操作后&#xff0c;达到的效果是想使用群晖生态软件&#xff0c;就必须要在服务端安装群晖系统&#xff0c;具体如何安装群晖虚拟机请参考&#xff1a; 1. 安装并配置synology drive1.1 安装群辉drive套件1.2 在局域…

本地PHP搭建简单Imagewheel私人云图床,在外远程访问

&#x1f525;博客主页&#xff1a; 小羊失眠啦 &#x1f516;系列专栏&#xff1a; C语言、Linux &#x1f325;️每日语录&#xff1a;追逐影子的人&#xff0c;自己就是影子。 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 1.前言 云存储在前几年风头无两&#xff0c;云存…

uniapp安卓 华为商店 vivo商店 oppo 小米 上架问题 Android中怎么才能不提前申请权限

问题描述 提交 小米 oppo 市场审核失败&#xff0c;原因是提前向用户申请开启通讯录、定位、短信、录音、相机和日历等权限。 解决方案&#xff1a; 是否有使用 READ_PHONE_STATE 权限&#xff0c;如果有使用 oaid 代替&#xff1b;是否有使用第三方 SDK&#xff0c;如果有关…

外部统一设置了::-webkit-scrollbar { display: none; }如何单独给特定元素开启滚动条设置样式-web页面滚动条样式设置

如果你在外部统一设置了​​::-webkit-scrollbar { display: none; }​​​来隐藏滚动条&#xff0c;但是想要在​​.lever​​元素中单独开启滚动条的样式&#xff0c;你可以使用CSS的级联选择器来覆盖外部样式。 以下是一个示例&#xff0c;展示如何给​​.lever​​单独开启…

什么是实验室超声消泡机?工作原理是怎样的?

超声波消泡设备也叫超声波脱气机、超声波消泡机、超声波消泡器。超声波在液体中产生空化作用,使得液体中溶解的气体(如:空气)不断凝聚,成为很细小的气泡,最后成为球状气泡脱离液体表面&#xff0c;从而达到液体脱气、液体消泡的目的。 实验室超声消泡机工作原理&#xff1a; …

13年测试老鸟,性能测试内存泄露——案例分析(超细整理)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、环境配置 1&a…

第二证券:国际油价大幅上涨 后市恐难持续走高

上个买卖周&#xff0c;受巴以冲突影响&#xff0c;原油商场成为各方关注的焦点。到上星期五收盘&#xff0c;布伦特原油周内涨幅达7%以上&#xff0c;为本年2月以来最大周涨幅&#xff0c;WTI原油周内累计上涨近6%。业内人士认为&#xff0c;其时地缘要素是导致油价出现异动的…

systemverilog/uvm的 blog https://www.amiq.com/consulting/blog/

有很多systemverilog/uvm的 blog https://www.amiq.com/consulting/blog/

字符串排序程序

字符串排序程序&#xff0c;对一个字符串中的数值进行从小到大的排序 例如排序前给定的字符串为" 20 78 9 -7 88 36 29" 排序后&#xff1a; -7 9 20 29 36 78 88 要求使用包装类对数值类型的字符串转换成整型进行排序。 public class StringSort {public static vo…

SpringBoot学习日记

Spring程序与SpringBoot程序对比 SpringBoot程序优点 起步依赖&#xff08;简化依赖配置&#xff09;自动装配&#xff08;简化常用工程相关配置&#xff09;辅助功能&#xff08;内置服务器&#xff0c;......&#xff09; 内嵌Tomcat REST风格 REST简介 REST&#xff0c;表…

.Net Core 6 运行环境手动安装流程

安装.NET Core 6 概述 在开始之前&#xff0c;我们首先需要了解一下整个安装过程的流程。下面的表格将展示安装.NET Core 6的步骤以及每一步需要做的事情。 步骤 动作 说明 1 下载.NET Core 6 SDK 从官方网站下载.NET Core 6 SDK安装包 2 安装.NET Core 6 SDK …

山西电力市场日前价格预测【2023-10-16】

日前价格预测 预测说明&#xff1a; 如上图所示&#xff0c;预测明日&#xff08;2023-10-16&#xff09;山西电力市场全天平均日前电价为356.38元/MWh。其中&#xff0c;最高日前电价为502.82元/MWh&#xff0c;预计出现在18: 30。最低日前电价为224.63元/MWh&#xff0c;预计…

长沙上市公司董秘联谊会,来啦!

上市公司的数量&#xff0c;是判断一座城市经济实力的重要指标。 在当前复杂的竞争环境中&#xff0c;提升上市公司的数量和质量&#xff0c;以产业思维促进城市内外的上市公司合作交流&#xff0c;是城市提升经济综合实力的有效举措。 10月13日&#xff0c;在由长沙市委统战…

享搭低代码平台:加速企业应用开发,轻松搭建表单和报表

在当今快节奏的商业环境中&#xff0c;企业需要快速响应市场需求并提供高效的解决方案。然而&#xff0c;传统的应用开发过程繁琐、耗时&#xff0c;并且需要专业的编程技能。为了解决这些问题&#xff0c;享搭低代码平台应运而生。本文将详细介绍享搭低代码平台的特点和优势&a…

Android Studio Giraffe | 2022.3.1

Android Gradle 插件和 Android Studio 兼容性 Android Studio 构建系统以 Gradle 为基础&#xff0c;并且 Android Gradle 插件 (AGP) 添加了几项专用于构建 Android 应用的功能。下表列出了各个 Android Studio 版本所需的 AGP 版本。 如果您的项目不受某个特定版本的 Andr…

springboot+html实现简单注册登录

前端&#xff1a; register.html <!DOCTYPE html> <html lang"en" > <head><meta charset"UTF-8"><title>register</title><link rel"stylesheet" type"text/css" href"/css/style.css&…

智慧水利:山海鲸数字孪生的革新之路

一、概念 什么是港口&#xff1f; "港口"通常指的是一个水域或岸边的设施&#xff0c;用于装载、卸载、储存和处理货物、以及提供与海上、河流或湖泊交通相关的服务。港口可以包括各种类型的码头、码头设备、仓库、货物运输设施、以及各种管理和物流设施。 什么是数…

Protocols/面向协议编程, DependencyInjection/依赖式注入 的使用

1. Protocols 定义实现协议&#xff0c;面向协议编码 1.1 创建面向协议实例 ProtocolsBootcamp.swift import SwiftUI/// 颜色样式协议 protocol ColorThemeProtocol {var primary: Color { get }var secondary: Color { get }var tertiary: Color { get } }struct DefaultCol…

UPS监控技术,你一定要试试,太绝了!

UPS&#xff08;不间断电源&#xff09;监控系统是一种关键的技术&#xff0c;用于监视、管理和维护不间断电源设备&#xff0c;以确保电力供应的稳定性和可用性。这对于各种组织和企业来说至关重要&#xff0c;因为电力中断可能导致生产中断、数据丢失和设备损坏&#xff0c;对…