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

news2024/11/20 0:40:33

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

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

最长公共子序列

这是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  ... j
    int 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  ... j
    int 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.coloradmin.cn/o/1094915.html

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

相关文章

三种对象注入的区别以及@Autowired和@Resource的区别

文章目录 1. 对象注入1.2 属性注入1.2.1 属性注入的优缺点 1.3 Setter方法注入1.3.1 Setter注入的优缺点 1.4 构造方法注入&#xff08;官方推荐&#xff09;1.4.1 构造方法的优缺点 1.5 Resource与Autowired区别 1. 对象注入 获取Bean对象也称为对象注入/对象装配&#xff0c…

Linux:Termius连接本地虚拟机与虚拟机快照

Termius连接本地虚拟机与虚拟机快照 1. Termius连接本地虚拟机2. 虚拟机快照与还原2.1 设置快照以及恢复 附录 1. Termius连接本地虚拟机 ifconfig -a 查看配置 连接成功 2. 虚拟机快照与还原 在学习阶段我们无法避免的可能损坏Linux操作系统。 如果损坏的话&#xff0c;重新…

FPGA复习(功耗)

减小功耗 就得减小电流 电流和CF有关&#xff08; C: 电容&#xff08;被门数目和布线长度影响&#xff09; F:时钟频率&#xff09; 方法大纲 减小功耗&#xff1a;1 时钟控制 2输入控制 3减小供电电压 4双沿触发器 5修改终端 同步数字电路降低动态功耗&#xff1a;动态禁止…

零食百货经营商城小程序的作用是什么

零食可以只指某款单品&#xff0c;也可以是一堆各品牌食品&#xff0c;其行业涵盖人群广泛&#xff0c;主要以零售和批发为主&#xff0c;不受限制&#xff0c;各地从业商家也非常多&#xff0c;但随着线上电商崛起&#xff0c;零食经营痛点也逐渐凸显。 通过【雨科】平台搭建零…

Elasticsearch实现检索词自动补全(检索词补全,自动纠错,拼音补全,繁简转换) 包含demo

Elasticsearch实现检索词自动补全 自动补全定义映射字段建立索引测试自动补全 自动纠错查询语句查询结果 拼音补全与繁简转换安装 elasticsearch-analysis-pinyin 插件定义索引与映射建立拼音自动补全索引测试拼音自动补全测试繁简转换自动补全 代码实现demo结构demo获取 自动补…

C# 图解教程 第5版 —— 第5章 类的基本概念

文章目录 5.1 类的概述5.2 程序和类&#xff1a;一个简单的示例&#xff08;*&#xff09;5.3 声明类&#xff08;*&#xff09;5.4 类成员&#xff08;*&#xff09;5.4.1 字段&#xff08;*&#xff09;5.4.2 方法 5.5 创建变量和类的实例&#xff08;*&#xff09;5.6 为数据…

解析找不到msvcp140.dll的5个解决方法,快速修复dll丢失问题

​在使用计算机过程中&#xff0c;我们也会遇到各种各样的问题。其中&#xff0c;找不到msvcp140.dll修复方法是一个非常普遍的问题。msvcp140.dll是一个动态链接库文件&#xff0c;它是Microsoft Visual C 2015 Redistributable的一部分。这个文件包含了许多用于运行C程序的函…

计算机网络基础(三):IPv4编址方式、子网划分、IPv4通信的建立与验证及ICMP协议

**IPv4地址是一个32位长的二进制数。**而这个32位二进制数又通常会表示为4个用点隔开的十进制数。那么&#xff0c;这个32位二进制数要如何通过4个十进制数表示出来呢&#xff1f; 我们在配置IPv4地址时&#xff0c;同时配置的“掩码”又有何用途&#xff1f; 1.IPv4编址方式…

MySQL 约束,视图,索引及常见函数

​​​​​​ ​​​​​​​ 2-MySQL 约束,视图,索引及常见函 1 SQL约束 SQL 约束用于规定表中的数据规则。实际上就是表中数据的限制条件。是为了保证数据的完整性而实现的一套机制。 MySQL的约束种类如下&#xff1a; 非空约束&#xff1a;NOT NULL NOT NULL约束强制…

汉堡炸鸡快餐店商城小程序的作用是什么

汉堡炸鸡等快餐店是不少年轻人常去的餐饮店&#xff0c;市场中除了头部品牌外&#xff0c;还有不少中小品牌&#xff0c;消费者选择度高&#xff0c;然而在实际经营中&#xff0c;面对线下流量匮乏、互联网电商发展&#xff0c;快餐店经营痛点不少。 对炸鸡汉堡店来说&#xf…

全球产业链:脑机接口产业链

本心、输入输出、结果 文章目录 全球产业链:脑机接口产业链前言马斯克旗下的脑机接口公司`Neuralink`宣布概念:什么是脑机接口脑机接口技术有哪几种路线脑机接口未来在各行业的应用脑机接口产业链上游脑机接口芯片脑电采集设备系统软件手术耗材脑机接口产业链中游脑机接口产业…

扩散模型的系统性学习(一):DDPM的学习

文章目录 一、学习的资料1.1 对于扩散模型的发展过程的综述1.2对论文中涉及的公式以及公式对应的代码的解读1.3github中对于各模型实现的代码1.4相关基础知识的学习 二、DDPM的学习2.1 DDPM总体知识的梳理2.2相关代码的解读2.2.1unet 代码块2.2.2高斯扩散代码块2.2.3 实验流程代…

【环境搭建】linux docker-compose安装seata1.6.1,使用nacos注册、db模式

新建目录&#xff0c;挂载用 mkdir -p /data/docker/seata/resources mkdir -p /data/docker/seata/logs 给权限 chmod -R 777 /data/docker/seata 先在/data/docker/seata目录编写一个使用file启动的docker-compose.yml文件&#xff08;seata包目录的script文件夹有&#…

常见的网络攻击手段

网络攻击对个人、组织和整个社会都带来了严重的威胁&#xff0c;因此必须采取有效的安全措施来保护网络系统和用户的信息安全。网站是攻击者经常瞄准的目标&#xff0c;以下是一些常见的攻击方式&#xff1a; 1. DDoS攻击&#xff08;分布式拒绝服务攻击&#xff09;&#xff1…

Unity引擎:收费模式和服务升级,为游戏开发带来更多可能性

Unity 引擎的收费模式和配套服务升级已经引起了广泛的关注和讨论。自 2024 年 1 月 1 日起&#xff0c;Unity 将根据游戏的安装量对开发者进行收费。这将会影响到很多游戏开发者和玩家。本文将探讨 Unity 引擎的收费模式和配套服务更新&#xff0c;以及对游戏开发者和玩家的影响…

数据结构 - 6(优先级队列(堆)13000字详解)

一&#xff1a;堆 1.1 堆的基本概念 堆分为两种&#xff1a;大堆和小堆。它们之间的区别在于元素在堆中的排列顺序和访问方式。 大堆&#xff08;Max Heap&#xff09;&#xff1a; 在大堆中&#xff0c;父节点的值比它的子节点的值要大。也就是说&#xff0c;堆的根节点是堆…

四川竹哲电子商务有限公司怎么样?可靠吗?

随着抖音等短视频平台的火热发展&#xff0c;越来越多的人开始关注如何在抖音上获得更多的关注和粉丝。而四川竹哲电子商务有限公司作为一家专业的抖音培训服务公司&#xff0c;正是帮助这些人实现梦想的地方。 首先&#xff0c;四川竹哲电子商务有限公司的抖音培训服务有着丰…

【软考】14.1 面向对象基本概念/分析设计测试

《面向对象开发》 对象 现实生活中实际存在的一个实体&#xff1b;构成系统的一个基本单位由对象名、属性和方法组成 类 实体的形式化描述&#xff1b;对象是类的实例&#xff0c;类是对象的模板可分为&#xff1a;实体类&#xff1a;现实世界中真实的实体接口类&#xff08;边…

msvcp120.dll丢失的解决方法总结,快速解决dll丢失问题

在计算机系统中&#xff0c;DLL&#xff08;动态链接库&#xff09;是一个重要的组成部分&#xff0c;它负责程序之间的相互调用和数据共享。然而&#xff0c;有时候我们可能会遇到“MSVCP120.dll丢失”的问题&#xff0c;这可能会导致一些应用程序无法正常运行。本文将详细介绍…

Netty P1 NIO 基础,网络编程

Netty P1 NIO 基础&#xff0c;网络编程 教程地址&#xff1a;https://www.bilibili.com/video/BV1py4y1E7oA https://nyimac.gitee.io/2021/04/25/Netty%E5%9F%BA%E7%A1%80/ 1. 三大组件 1.1 Channel & Buffer Channel 类似 Stream&#xff0c;它是读写数据的双向通道…