Day43【动态规划】1049.最后一块石头的重量 II、494.目标和、474.一和零

news2024/9/28 15:30:48

1049.最后一块石头的重量 II

力扣题目链接/文章讲解

视频讲解

还是需要转化为 0-1 背包问题:物品装入背包,求装入的最大价值(每个物品至多装入一次)

要把01背包问题套到本题上来,需要确定

  • 背包容量
  • 物品价值
  • 物品重量

转化的核心思路:尽量让石头分成重量相同的两堆,则这两堆相撞之后剩下的石头最小

我们希望尽可能凑成重量为 sum / 2 的石头堆,假如我们将石头看成一定重量的物品,问题转化成想将这些具有一定重量的物品装入容量为 sum / 2 的背包,看看最大能装多少重量

我们只需要设置物品重量和价值相等,则能装多少重量  = 装入的最大价值 

这样问题就转过来了: 

  • 背包容量:sum / 2
  • 物品价值:石头重量
  • 物品重量:石头重量

剩下的套 0-1 背包问题滚动数组的解题思路就好 

代码如下 

class Solution {
public:
    int lastStoneWeightII(vector<int>& stones) {
        int sum = accumulate(stones.begin(), stones.end(), 0);
        int bagSize = sum / 2;
        // 背包容量bagSize
        // 物品重量stones
        // 物品价值stones
        vector<int> dp(bagSize + 1);    // 滚动数组,下标代表背包容量,值代表装入的最大价值
        // 初始化原二维dp数组的第一行,即将物品0装入容量为j的背包
        for (int j = 0; j <= bagSize; ++j) {
            if (j >= stones[0]) // 背包容量大于等于物品0的重量
                dp[j] = stones[0];  // 物品0的价值
            else    // 背包装不下物品0
                dp[j] = 0;
        }
        for (int i = 1; i < stones.size(); ++i) {   // 从第二行(i=1)开始遍历填充原二维dp数组
            for (int j = bagSize; j >= 0; --j) {    // 倒序填充原二维dp数组每一行的元素
                if (j >= stones[i]) // 容量j能装下物品。注意右边的dp表示的是i-1层的dp
                    dp[j] = max(dp[j], dp[j - stones[i]] + stones[i]);
                else    // 装不下。注意右边的dp表示的是i-1层的dp
                    dp[j] = dp[j];
            }
        }
        return (sum - dp[bagSize]) - dp[bagSize];   // 第一个括号里的值为另一堆石头的重量,dp[bagSize]为这一堆石头的重量
    }
};

注意上述代码在 for 循环中控制条件可以进行一定程度的优化,但是我们给出最直观的代码逻辑 

494.目标和

力扣题目链接/文章讲解

视频讲解

本题可以用回溯,但是大概率超时

还是用 0-1 背包的思路

如何转化为 0-1 背包问题呢

假设加法的总和为 bagSize,那么减法对应的总和就是 sum - bagSize

所以我们要求的是 bagSize - (sum - bagSize) = target

bagSize = (target + sum) / 2

此时问题就转化为,装满容量为 bagSize 的背包,有几种方法

好像问题有变化,变成了装满背包的方法数,没办法按照之前物品装包求最大价值的思维模式了

好吧,那就还是从动态规划五部曲开始,由二维 dp 数组推导吧

1、确定 dp 数组下标及值的含义 

dp[i][j]:下标 i, j 表示从下标为 [0 - i] 的物品中任意取装满容量为 j 的背包,dp[i][j] 的值表示从物品下标 [0 - i] 任取物品装满容量为 j 的背包有多少种方法

2、确定递推公式 

dp[i][j] 的值表示从下标为 [0 - i] 的物品中任取,装满容量为 j 的背包的方法数,怎么求这个 dp[i][j] 呢?为了求 dp[i][j],肯定需要考虑从下标为 0 到 i 的物品中取物然后装满容量为 j 的背包的装入方案。得到这个 dp[i][j] 的方案有两种:一种是不放物品 i 装满了背包,另一种是放了物品 i 装满了背包

  • 不放物品 i:已经确定里面不放物品 i 的装入方法数,为物品 0 到 i - 1 装满容量为 j 的背包的方法数,即 dp[i - 1][j]
  • 放物品 i:已经确定要放物品 i 时的装入方法数,为物品 0 到 i - 1 装满容量为 j - nums[i] 的背包的方法数,即 dp[i - 1][j - nums[i]]

两种方案各自方法数加起来就是总的装满容量为 j 的背包的方法数,即递推公式:dp[i][j] = dp[i - 1][j] + dp[i - 1][j - nums[i]] 

注意这个递推公式的前提条件是背包要能装下物品 i,否则只需要考虑不放物品 i 的方案 

3、dp 数组初始化

由递推公式,我们首先观察到 dp[i][j] 的值都是由上一行(层)的值推出来的(即由 i - 1 那一行推出来),故一定要初始化第一行,即 dp[0][j] 需要被初始化

怎么初始化 dp[0][j] 呢?考虑 dp 数组下标及值的含义:取物品 0(从下标为 0 到 0 的物品中取)装满容量为 j 的背包的方法数 

  • 当物品0重量非0,则当背包容量为0时有一种装满方案(啥也不装),当背包容量为物品0的重量时有一种装满方案(就把物品0装进去),其他背包容量都无法装满
  • 当物品0重量为0,则当背包容量为0时候有一种装满方案(啥也不装或装入物品0),其他背包容量都无法装满

第二行及之后的位置随意初始化,反正都会被遍历覆盖

4、确定遍历顺序 

从上层(上一行)遍历填充到下层(下一行)就可以(因为当前层的值是由上一层推出来的,需要保证上一层是已经更新后的正确的值),单层中从左到右或者从右到左遍历填充均可 

代码中,外层 for 循环遍历物品即可(一个物品的索引代表一行,一行一行遍历) 

5、打印dp数组验证

代码如下

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int target) {
        int sum = accumulate(nums.begin(), nums.end(), 0);
        if ((target + sum) % 2 == 1) return 0; // 此时没有方案
        if (abs(target) > sum) return 0; // 此时没有方案
        int bagSize = (target + sum) / 2;   // 加法的总和(背包容量)
        
        // 背包容量:bagSize
        // 物品重量:nums
        // 求装满背包有多少种方法
        
        //1、确定 dp 数组下标及值的含义:下标为从物品0 - i选择取装满容量为j的背包,值为选择物品装满背包的方法数
        vector<vector<int> > dp(nums.size(), vector<int>(bagSize + 1));
        //2、确定递推公式:dp[i][j] = dp[i - 1][j] + dp[i - 1][j - nums[i]]
        //3、dp数组初始化:由递推公式,我们首先观察到 dp[i][j] 的值都是由上一行(层)的值推出来的(即由 i - 1 那一行推出来),故一定要初始化第一行,即 dp[0][j] 需要被初始化 
        for (int j = 0; j <= bagSize; ++j) {
            if (nums[0] != 0) {
                if (j == 0 || j == nums[0]) // 当背包容量为0或背包容量为物品0的重量时,能用物品0装满
                    dp[0][j] = 1;
                else
                    dp[0][j] = 0;
            }
            else    // 物品重量为0
            {
                if (j ==  0)    // 背包容量为0时,可以不装,可以装物品0,两种方案
                    dp[0][j] = 2;
                else
                    dp[0][j] = 0;
            }
        }
        //4、遍历填充dp数组:一行一行填充
        for (int i = 1; i < nums.size(); ++i) { // 遍历物品(遍历填充每一行)
            for (int j = 0; j <= bagSize; ++j)  // 遍历背包容量(遍历填充每一行中的元素)
                if (j >= nums[i])   // 容量j能放下物品i的重量
                    dp[i][j] = dp[i - 1][j] + dp[i - 1][j - nums[i]];
                else
                    dp[i][j] = dp[i - 1][j];
        }
        //5、打印dp数组验证略
        return dp[nums.size() - 1][bagSize];
    }
};

本题同样,dp[i][j] 的值都是由上一行(层)的值推出来的,也可以利用滚动数组进行状态压缩,这里给出代码实现。递推公式左边的 dp 是第 i 层,右边的 dp 是第 i - 1 层

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int target) {
        int sum = accumulate(nums.begin(), nums.end(), 0);
        if ((target + sum) % 2 == 1) return 0; // 此时没有方案
        if (abs(target) > sum) return 0; // 此时没有方案
        int bagSize = (target + sum) / 2;   // 加法的总和(背包容量)
        
        // 背包容量:bagSize
        // 物品重量:nums
        // 求装满背包有多少种方法
        
        //1、确定 dp 数组下标及值的含义:下标为装满容量为j的背包,值为装满背包的方法数
        vector<int> dp(bagSize + 1);
        //2、确定递推公式:dp[j] = dp[j] + [j - nums[i]]
        //3、dp数组初始化:一定要初始化原二维dp数组第一行
        for (int j = 0; j <= bagSize; ++j) {
            if (nums[0] != 0) {
                if (j == 0 || j == nums[0]) // 背包容量为0或物品0的重量,能用物品0装满
                    dp[j] = 1;
                else
                    dp[j] = 0;
            }
            else    // 物品重量为0
            {
                if (j ==  0)    // 可以不装,可以装物品0,两种方案
                    dp[j] = 2;
                else
                    dp[j] = 0;
            }
        }
        //4、遍历填充原二维dp数组:一行一行填充
        for (int i = 1; i < nums.size(); ++i) { // 遍历物品(遍历填充原二维数组每一行)
            for (int j = bagSize; j >= 0; --j)  // 遍历背包容量(遍历填充每一行中的元素),需要倒序遍历
                if (j >= nums[i])   // 容量j能放下物品i的重量
                    dp[j] = dp[j] + dp[j - nums[i]];
                else
                    dp[j] = dp[j];
        }
        //5、打印dp数组验证略
        return dp[bagSize];
    }
};

474.一和零 

力扣题目链接/文章讲解

视频讲解

本题转化为 0-1 背包问题:物品装入背包(一个物品最多装一次),求最大的装入物品数量

该题目容量/重量是两个维度

怎么理解容量/重量有两个维度?打个比方,一个背包的容量能装重量为 M 的固体和重量为 N 的液体,每个物品由重量为 m 的固体部分和重量为 n 的液体部分构成。若某个物品能装进背包,需要物品的固体部分总重量和液体部分总重量均分别小于等于背包对于固体的容量及对于液体的容量,才能放进 

但是本质上该题还是 0-1 背包,因为一个物体只能被选一次 

来,动态规划三部曲!

1、确定 dp 数组下标及值的含义 

dp[i][j][k]:下标 i, j, k 表示从下标为 [0 - i] 的物品中任意取装入有两个容量维度的背包,这两个容量维度分别为 j 和 k,dp[i][j][k] 的值表示从物品下标 [0 - i] 任取物品装进背包最多能装入多少个物品

2、确定递推公式  

为了求 dp[i][j][k],肯定需要考虑从下标为 0 到 i 的物品中取物然后装进有两个容量维度的背包的装入方案。得到这个 dp[i][j][k] 的方案有两种:一种是不装物品 i 进背包,另一种是装物品 i 进背包

  • 不放物品 i:已经确定里面不放物品 i,则最大装入物品数量是从物品 0 到 i-1 中选择物品装入容量维度为 j k 的背包的最大装入物品数量,即 dp[i - 1][j][k]
  • 放物品 i:已经确定要放物品 i ,则最大装入物品数量是从物品 0 到 i-1 中选择物品装入容量维度为 j - 物品 i 所占的该维度容量k - 物品 i 所占的该维度容量的背包的最大装入物品数量加一,即 dp[i - 1][j - weight0[i]][k - weight1[i]] + 1

两种方案的最大值就是 dp[i][j][k],即:dp[i][j][k] = max(dp[i-1][j-weight0[i]][k-weight1[i]] + 1, dp[i-1][j][k])

注意这个递推公式的前提条件是背包要能装下物品 i,否则只需要考虑不放物品 i 的方案  

3、dp 数组初始化

由递推公式,我们首先观察到 dp[i][j][k] 的值都是由 i - 1 那个维度的 dp 值推出来的,故 dp[0][j][k] 需要被初始化

怎么初始化也很简单,结合 dp 数组下标含义:若物品 0 能够放进容量维度分别为 j 和 k 的背包,则最多能装入一个物品,dp[0][j][k] 为1;若装不下物品 0, 则一个物品也装不进去,dp[0][j][k] 为 0

4、确定遍历顺序 

从上层遍历填充到下层就可以(因为当前层 i 的值是由上一层 i - 1 的值推出来的,需要保证上一层是已经更新后的正确的值),单层中任意顺序遍历填充均可 

代码中,外层 for 循环遍历物品即可

5、打印 dp 数组验证

代码如下

class Solution {
public:
    int findMaxForm(vector<string>& strs, int m, int n) {
        vector<int> weight0, weight1;
        for (auto & str : strs) {
            int num0 = 0;
            int num1 = 0;
            for (auto c : str)
            {
                if (c == '0')
                    ++num0;
                if (c == '1')
                    ++num1;
            }
            weight0.push_back(num0);
            weight1.push_back(num1);
        }   // 统计每个物品的两个重量维度
        // 背包两个容量维度分别为 m 和 n

        // 定义dp数组
        vector<vector<vector<int> > > dp(strs.size(), vector<vector<int> >(m + 1, vector<int>(n + 1)));
        // 初始化dp数组
        for (int j = 0; j <= m; ++j) {
            for (int k = 0; k <= n; ++k) {
                if (j >= weight0[0] && k >= weight1[0]) // 若物品0能放进背包
                    dp[0][j][k] = 1;
                else    // 从物品0中任选,一个物品也放不进
                    dp[0][j][k] = 0;
            }
        }
        // 遍历。从i为1开始遍历填充
        for (int i = 1; i < strs.size(); ++i) {
            for (int j = 0; j <= m; ++j)
                for (int k = 0; k <= n; ++k) {
                    if (j >= weight0[i] && k >= weight1[i]) {   // 若物品i能放进背包
                        dp[i][j][k] = max(dp[i-1][j-weight0[i]][k-weight1[i]] + 1, dp[i-1][j][k]);
                    }
                    else    // 物品i不能放进背包
                        dp[i][j][k] = dp[i-1][j][k];
                }
        }
        return dp[strs.size() - 1][m][n];
    }
};

同样,本题也可以状态压缩

class Solution {
public:
    int findMaxForm(vector<string>& strs, int m, int n) {
        vector<int> weight0, weight1;
        for (auto & str : strs) {
            int num0 = 0;
            int num1 = 0;
            for (auto c : str)
            {
                if (c == '0')
                    ++num0;
                if (c == '1')
                    ++num1;
            }
            weight0.push_back(num0);
            weight1.push_back(num1);
        }   // 统计每个物品的两个重量维度
        // 背包两个容量维度分别为 m 和 n

        // 定义dp数组
        vector<vector<int> > dp(m + 1, vector<int>(n + 1));
        // 初始化dp数组
        for (int j = 0; j <= m; ++j) {
            for (int k = 0; k <= n; ++k) {
                if (j >= weight0[0] && k >= weight1[0]) // 若物品0能放进背包
                    dp[j][k] = 1;
                else    // 从物品0中任选,一个物品也放不进
                    dp[j][k] = 0;
            }
        }
        // 遍历。从i为1开始遍历填充
        for (int i = 1; i < strs.size(); ++i) {
            for (int j = m; j >= 0; --j)
                for (int k = n; k >= 0; --k) {  // 需要倒序遍历!这里是二维的,即需要从下往上,从右往左遍历填充
                    if (j >= weight0[i] && k >= weight1[i]) {   // 若物品i能放进背包
                        dp[j][k] = max(dp[j-weight0[i]][k-weight1[i]] + 1, dp[j][k]);
                    }
                    else    // 物品i不能放进背包
                        dp[j][k] = dp[j][k];
                }
        }
        return dp[m][n];
    }
};

为什么需要倒序遍历? 

根据非滚动数组版本的递推公式,我们更新当前层 i 层某位置元素依赖的是上一层 i - 1 层的元素,更详细地说是上一层对应当前位置及对应当前位置左上角的元素(注意这里每层是二维数组),对应到滚动数组中,更新某位置元素依赖的是当前位置元素的旧值及当前位置左上角的元素的旧值(这里的旧值指的是上一轮外层 for 循环所填充的值)。只有倒序遍历从下向上,从右向左填充,才能保证当前位置及当前位置左上角的元素保留上一轮外层循环的旧值。如果正序遍历,则当前位置的左上角元素的值可能在本轮外层循环中已经被更新了,无法持有上一轮循环更新得到的值了


回顾总结 

我们用0-1背包解决了哪些问题? 

物品装入背包的最大价值

物品能否装满背包

背包最多能装多重的物品

物品装满背包有多少种方法

装满最多有多少个物品 

时刻记住滚动 dp 数组与二维 dp 数组是如何对应的!滚动数组递推公式右边是 i - 1 层,左边是 i 层

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

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

相关文章

分布式消息中间件RocketMQ的应用

RocketMQ 应用 所有代码同步至GitCode&#xff1a;https://gitcode.net/ruozhuliufeng/test-rocketmq.git 普通消息 消息发送分类 ​ Producer对于消息的发送方式也有多种选择&#xff0c;不同的方式会产生不同的系统效果。 同步发送消息 ​ 同步发送消息是指&#xff0c;P…

Win11或Win10重置电脑提示“找不到恢复环境”

想要重置电脑缺提示找不到恢复环境 查看是否开启功能 按住“winx”选A管理员运行终端&#xff0c;输入reagentc /info。 如果信息结果如下&#xff1a; Windows RE 状态: DisabledWindows RE 位置:引导配置数据(BCD)标识符: cedd8faa-707a-11ed-ad72-a8056da9f4d6…

头歌计算机组成原理实验—运算器设计(3)第3关:4位快速加法器设计

第3关&#xff1a;4位快速加法器设计 实验目的 帮助学生掌握快速加法器中先行进位的原理&#xff0c;能利用相关知识设计4位先行进位电路&#xff0c;并利用设计的4位先行进位电路构造4位快速加法器&#xff0c;能分析对应电路的时间延迟。 视频讲解 实验内容 利用前一步设…

Learning C++ No.23【红黑树封装set和map】

引言 北京时间&#xff1a;2023/5/17/22:19&#xff0c;不知道是以前学的不够扎实&#xff0c;还是很久没有学习相关知识&#xff0c;对有的知识可以说是遗忘了许多&#xff0c;以该篇博客有关知识为例&#xff0c;我发现我对迭代器和模板的有关知识的理解还不够透彻&#xff…

音视频源码调试前准备vs2019+qt5.15.2搭建可调试环境

安装vs2019qt,并且在windows环境上安装ffmpeg&#xff0c;尝试使用qtcdb进行调试&#xff0c;尝试使用vs2019加载qt的程序。 安装VS20195.12.2qt环境&#xff0c;并进行测试。 1&#xff1a;安装Visual Studio 2019, a.从官网下载&#xff0c;或者vs2019社区版本下载地址 ht…

SNAP软件处理Sentinel-2 L2A数据为hdr或者tif文件

1.打开Sen2Cor插件处理好的或者下载好的L2A文件 若不知道如何将下载的L1C数据处理为L2A级数据可查看该篇博文 Sentinel-2数据下载及处理_dropoutgirl的博客-CSDN博客 在Bands文件夹下少了B10波段栅格文件: 这主要是因为波段10是卷云波段&#xff0c;需要的大气顶部&#xff0…

顺序表之线性表(难度:✨)

1.线性表 线性表呈现出一条线性&#xff0c;用指针把一块一块的内存连接起来。 其余还有树型结构&#xff0c;哈希结构&#xff0c;图结构。 线性表分为&#xff1a; 顺序表链表栈队列字符串 1.2顺序表 顺序表就是数组&#xff0c;但在数组的基础上&#xff0c;从头开始存。…

地下车库CO传感器报警系统

前言 在现代城市中&#xff0c;地下车库已经成为了不可或缺的交通设施。然而&#xff0c;在地下车库中&#xff0c;由于车辆尾气等因素&#xff0c;很容易出现CO中毒的风险&#xff0c;给车库内的人员带来威胁。本文将对地下车库CO传感器报警系统进行介绍和分析&#xff0c;包…

21级计科专业计算机组成原理实验考试(体验)

在使用VC6.0软件时&#xff0c;为了进入调试模式&#xff0c;需要先点击【Build】&#xff0c;再点击&#xff08; &#xff09; A. BuildExecute B. Go C. Execute D. Compile 在使用VC6.0软件进入调试模式后&#xff0c;点击【View】→【Debug Windows】后的&#xff08; &…

Shell脚本常见用法列举

前言 最近在复习shell脚本的相关知识&#xff0c;本文列举了一些在shell脚本中用得到的一些基础语法。 1&#xff1a;打印常见内部变量和环境变量值 shell中常用变量介绍 $0脚本名$n第n个参数&#xff0c;n1,2,3...$*所有参数列表&#xff08;视为一个整体&#xff0c;不包…

chatgpt赋能Python-python3人脸识别

人脸识别的python3应用&#xff1a;一步步实现高精度的面部识别 Python3作为一种高效的编程语言&#xff0c;具有广泛的应用场景。近年来&#xff0c;人脸识别技术在安防、金融、医疗等领域中逐渐普及&#xff0c;运用Python3进行人脸识别具有巨大的潜力。本文将介绍如何使用P…

React学习笔记四-state

此文章是本人在学习React的时候&#xff0c;写下的学习笔记&#xff0c;在此纪录和分享。此为第四篇&#xff0c;主要介绍react中的state。 1.state&#xff08;状态&#xff09; 1.1state简介 存在state(状态)的组件称为复杂组件&#xff0c;反之称为简单组件。 何为状态呢…

TCL表达式

目录 操作数 运算符和优先级 数学函数 操作数 TCL 表达式的操作数通常是整数或实数。整数一般是十进制的&#xff0c; 但如果整数的第一个字符是 0(zero)&#xff0c;那么 TCL 将把这个整数看作八进制的&#xff0c;如果前两个字符是 0x 则这个整数被看作是十 六进制的。TC…

I2C项目问题总结

1、项目里面用到哪些接口&#xff1f;都是怎么用的&#xff1f; 项目里面用到了rkv_i2c_if、lvc_i2c_if、lvc_apb_if。rkv_i2c_if用来将DUT内部的intr中断信号、debug信号、ic_en使能信号、i2c和apb的时钟复位信号引出&#xff0c;在接口中声明了一个大位宽信号用来表示intr中…

分布式消息中间件RocketMQ的工作原理

RocketMQ 工作原理 消息的生产 消息的生产过程 ​ Producer可以将消息写入到某Broker中的某Queue中&#xff0c;其经历了如下过程&#xff1a; Producer发送消息之前&#xff0c;会先向NameServer发出获取消息Topic的路由信息的请求NameServer返回该Topic的路由表及Broker列…

kubernetes yaml文件|--dry-run导出yaml文件

kubernetes yaml文件|--dry-run导出yaml文件 YAML 语法格式&#xff1a;二 查看 api 资源版本标签三 写一个nignx.yaml文件demo四、编写service服务的资源清单详解k8s中的port五 用–dry-run命令生成yaml资源清单六 将现有的资源生成模板导出写yaml太累怎么办&#xff1f; YAML…

关于FLAME和SMPL模型

英文参考文献&#xff1a;https://medium.com/offnote-labs/3d-face-and-body-reconstruction-95f59ada1040 一个训练好的FLAME模型的输入是一个参数向量&#xff0c;包括形状参数、姿势参数和表情参数。这些参数分别控制人脸的身份特征、头部的旋转和平移、面部的表情变化。一…

算法设计与分析:线性规划问题和单纯形算法(作业-必做)(头歌实验)

第1关&#xff1a;单纯性算法解一般线性方程组 任务描述 本关任务&#xff1a;编写一个利用两阶段单纯性算法求一般线性规划的程序。 相关知识 单纯形算法的第1步&#xff1a;选出使目标函数增加的非基本变量作为入基变量。 查看单纯形表的第 1 行&#xff08;也称之为z行…

UnityVR--Managers--对象池1

本篇中使用的API&#xff1a;gameObject.CompareTag("标签")、UnityEvent()事件管理、ObjectPool<GameObject>&#xff08;&#xff09;对象池 参照unity官方教程&#xff1a;Hi ObjectPool 目录 1. 应用场景 2. 对象池的原理 3. 查看资源消耗情况 4. 不使用…

mysql 备库延迟问题

备库延迟原因&#xff1a; log传送开销小&#xff0c;消费relay log 超时 备库性能不如主库 备库承担更多SQL分析 主库是多线程执行&#xff0c;备库是单线程执行解析relay log 处理方法&#xff1a; 主备使用相同的机器 备库关闭log实时落盘 增加从库数量&#xff0c;…