Leetcode经典题4--查找数组中的多数元素+Boyer-Moore 投票算法

题目描述

给定一个大小为 n 的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。

你可以假设数组是非空的,并且给定的数组总是存在多数元素。

输入输出示例

输入:nums = [2,2,1,1,1,2,2]

输出:2

解题方案

方式一:暴力枚举(不推荐)

算法思路:最简单的暴力方法是,枚举数组中的每个元素,再遍历一遍数组统计其出现次数。

复杂度分析

该方法的时间复杂度是 O(n2),会超出时间限制,因此我们需要找出时间复杂度小于 O(n2) 的优秀做法。

方式二:哈希表

算法思想:我们知道出现次数最多的元素大于 ⌊n/2⌋ 次,所以可以用哈希表来快速统计每个元素出现的次数。我们使用哈希映射(HashMap)来存储每个元素以及出现的次数。对于哈希映射中的每个键值对,键表示一个元素,值表示该元素出现的次数。

实现步骤:

  • 我们用一个循环遍历数组 nums 并将数组中的每个元素加入哈希映射中。
  • 在这之后,我们遍历哈希映射中的所有键值对,在遍历数组 nums 时候使用打擂台的方法,维护最大的值,返回值最大的键。

实现代码:

class Solution {//方法的作用:遍历数组中的元素,将元素以及其出现次数加入到Map集合当中private Map<Integer, Integer> countNums(int[] nums) {Map<Integer, Integer> counts = new HashMap<Integer, Integer>();for (int num : nums) {if (!counts.containsKey(num)) {//如果是第一次添加进Map集合的情况counts.put(num, 1);} else {//如果不是第一次添加,需要根据KEY获取对应的value进行添加counts.put(num, counts.get(num) + 1);}}return counts;}public int majorityElement(int[] nums) {//1.首先将数组中的元素放入到Map集合当中Map<Integer, Integer> counts = countNums(nums);//存储出现次数最多的元素Map.Entry<Integer, Integer> majorityEntry = null;for (Map.Entry<Integer, Integer> entry : counts.entrySet()) {if (majorityEntry == null || entry.getValue() > majorityEntry.getValue()) {majorityEntry = entry;}}return majorityEntry.getKey();}
}

Map.Entry

1.所属的类和接口体系

Map.Entry是一个接口,它定义在java.util包下,位于 Java 的集合框架中。这个接口是Map接口的一个内部接口,用于描述Map中的一个键值对元素(也就是一个映射关系,包含了对应的键以及与该键关联的值)。

Map接口常见的实现类有HashMap、TreeMap、LinkedHashMap等,而不管是哪种具体的Map实现类,它们内部存储的都是一个个的键值对,这些键值对在代码层面就可以用Map.Entry接口类型来进行操作和表示。

2.主要的方法和作用

Map.Entry接口提供了几个常用方法来获取和操作对应的键值对信息:

  • getKey()方法:用于获取该键值对中的键。例如,对于一个Map.Entry类型的变量entry,通过entry.getKey()就能得到对应的String类型的键。
  • getValue()方法:用于获取该键值对中的值。沿用上面的例子,通过entry.getValue()就能获取到对应的Integer类型的值。
  • setValue(V value)方法(这里的V是对应值的泛型类型):可以用于修改当前键值对中的值,不过要注意,并不是所有实现了Map.Entry接口的类都支持修改值操作,像HashMap中对应的Entry是支持的,而对于不可变的Map实现(如Collections.unmodifiableMap返回的Map)其内部的Entry就不支持修改值操作。

在代码中,经常会使用迭代器或者for-each循环遍历Map的entrySet()(这个方法返回一个包含所有Map.Entry元素的集合)来依次获取每个键值对,进而利用上述这些方法来操作其中的键和值信息,

复杂度分析

时间复杂度:O(n),

其中 n 是数组 nums 的长度。我们遍历数组 nums 一次,对于 nums 中的每一个元素,将其插入哈希表都只需要常数时间。如果在遍历时没有维护最大值,在遍历结束后还需要对哈希表进行遍历,因为哈希表中占用的空间为 O(n)(可参考下文的空间复杂度分析),那么遍历的时间不会超过 O(n)。因此总时间复杂度为 O(n)。

空间复杂度:O(n)。

哈希表最多包含 n−⌊n/2⌋ 个键值对,所以占用的空间为 O(n)。这是因为任意一个长度为 n 的数组最多只能包含 n 个不同的值,但题中保证 nums 一定有一个众数,会占用(最少) ⌊n/2⌋+1 个数字。因此最多有 n−(⌊n/2⌋+1) 个不同的其他数字,所以最多有 n−⌊n/2⌋ 个不同的元素。

方法三:排序

算法思想

如果将数组 nums 中的所有元素按照单调递增或单调递减的顺序排序,那么下标为 ⌊n/2⌋ 的元素(下标从 0 开始)一定是众数。

实现步骤

对于这种算法,我们先将 nums 数组排序,然后返回上文所说的下标对应的元素。

下面的图中解释了为什么这种策略是有效的。在下图中,第一个例子是 n 为奇数的情况,第二个例子是 n 为偶数的情况。

 对于每种情况,

数组上面的线表示如果众数是数组中的最小值时覆盖的下标,

数组下面的线表示如果众数是数组中的最大值时覆盖的下标。

对于其他的情况,这条线会在这两种极端情况的中间。

对于这两种极端情况,它们会在下标为 ⌊n/2⌋ 的地方有重叠。因此,无论众数是多少,返回 ⌊n/2⌋ 下标对应的值都是正确的。

class Solution {public int majorityElement(int[] nums) {Arrays.sort(nums);return nums[nums.length / 2];}
}

复杂度分析

时间复杂度:O(nlogn)。

将数组排序的时间复杂度为 O(nlogn)。

空间复杂度:O(logn)。

如果使用语言自带的排序算法,需要使用 O(logn) 的栈空间。如果自己编写堆排序,则只需要使用 O(1) 的额外空间。

方法四:随机化

算法思想

因为超过 ⌊n/2⌋ 的数组下标被众数占据了,这样我们随机挑选一个下标对应的元素并验证,有很大的概率能找到众数。

实现思路

由于一个给定的下标对应的数字很有可能是众数,我们随机挑选一个下标,检查它是否是众数,如果是就返回,否则继续随机挑选。

实现代码:

class Solution {//生成一个再区间[min,max)之间的随机数private int randRange(Random rand, int min, int max) {return rand.nextInt(max - min) + min;}//统计在数组nums当中,值为num的元素出现次数private int countOccurences(int[] nums, int num) {int count = 0;for (int i = 0; i < nums.length; i++) {if (nums[i] == num) {count++;}}return count;}public int majorityElement(int[] nums) {//1.定义一个随机数Random rand = new Random();int majorityCount = nums.length / 2;while (true) {//2.nums[randRange(rand, 0, nums.length)]为数组中的任意一个元素,并将值赋给candidate//randRange(rand, 0, nums.length)返回随机的一个数组下标int candidate = nums[randRange(rand, 0, nums.length)];//3.如果candidate的出现次数大于数组长度的1/2,那么该元素为多数元素if (countOccurences(nums, candidate) > majorityCount) {return candidate;}}}
}

复杂度分析

时间复杂度:无法推算

理论上最坏情况下的时间复杂度为 O(∞),因为如果我们的运气很差,这个算法会一直找不到众数,随机挑选无穷多次,所以最坏时间复杂度是没有上限的。

然而,运行的期望时间是线性的。为了更简单地分析,先说服你自己:由于众数占据 超过 数组一半的位置,期望的随机次数会小于众数占据数组恰好一半的情况。因此,我们可以计算随机的期望次数(下标为 prob 为原问题,mod 为众数恰好占据数组一半数目的问题):

计算方法为:

当众数恰好占据数组的一半时,第一次随机我们有1/2的概率找到众数,

如果没有找到,则第二次随机时,包含上一次我们有1/4的概率找到众数,

以此类推。因此期望的次数为 i∗1/2^i的和,可以计算出这个和为 2,说明期望的随机次数是常数。

每一次随机后,我们需要 O(n) 的时间判断这个数是否为众数,因此期望的时间复杂度为 O(n)。

空间复杂度:O(1)。

随机方法只需要常数级别的额外空间。

方式五:分治

算法思想

如果数 a 是数组 nums 的众数,如果我们将 nums 分成两部分,那么 a 必定是至少一部分的众数。

我们可以使用反证法来证明这个结论。

假设 a 既不是左半部分的众数,也不是右半部分的众数,那么 a 出现的次数少于 l / 2 + r / 2 次,其中 l 和 r 分别是左半部分和右半部分的长度。

由于 l / 2 + r / 2

实现思路

  • 将数组分成左右两部分
  • 分别求出左半部分的众数 a1 以及右半部分的众数 a2
  • 在 a1 和 a2 中选出正确的众数。

我们使用经典的分治算法递归求解,直到所有的子问题都是长度为 1 的数组。

长度为 1 的子数组中唯一的数显然是众数,直接返回即可。

如果回溯后某区间的长度大于 1,我们必须将左右子区间的值合并。

如果它们的众数相同,那么显然这一段区间的众数是它们相同的值。

否则,我们需要比较两个众数在整个区间内出现的次数来决定该区间的众数。

实现代码

class Solution {//统计元素num在区间[lo,hi]中出现的次数private int countInRange(int[] nums, int num, int lo, int hi) {int count = 0;for (int i = lo; i <= hi; i++) {if (nums[i] == num) {count++;}}return count;}private int majorityElementRec(int[] nums, int lo, int hi) {//在大小为 1 的数组中,唯一的那个元素就是多数元素if (lo == hi) {return nums[lo];}// 分成左右两部分,做递归int mid = (hi - lo) / 2 + lo;int left = majorityElementRec(nums, lo, mid);int right = majorityElementRec(nums, mid + 1, hi);//如果左右两部分返回的多数元素相同,则直接返回即可if (left == right) {return left;}//否则,统计两个元素在数组中的出现次数,返回真正的多数元素int leftCount = countInRange(nums, left, lo, hi);int rightCount = countInRange(nums, right, lo, hi);return leftCount > rightCount ? left : right;}public int majorityElement(int[] nums) {return majorityElementRec(nums, 0, nums.length - 1);}
}

复杂度分析

时间复杂度:O(nlogn)。

函数 majority_element_rec() 会求解 2 个长度为n/2的子问题,并做两遍长度为 n 的线性扫描。因此,分治算法的时间复杂度可以表示为:

       T(n)=2T(n/2)+2n

根据 主定理,本题满足第二种情况,所以时间复杂度可以表示为:

空间复杂度:O(logn)。

尽管分治算法没有直接分配额外的数组空间,但在递归的过程中使用了额外的栈空间。算法每次将数组从中间分成两部分,所以数组长度变为 1 之前需要进行 O(logn) 次递归,即空间复杂度为 O(logn)。

方法五:Boyer-Moore 投票算法 最优解法

算法思想

如果我们把众数记为 +1,把其他数记为 −1,将它们全部加起来,由于多数元素的出现次数超过数组元素的半数,显然和大于 0,从结果本身我们可以看出众数比其他数多。

实现步骤

Boyer-Moore 算法的本质和方法四中的分治十分类似。我们首先给出 Boyer-Moore 算法的详细步骤:

我们维护一个候选众数 candidate 和它出现的次数 count。初始时 candidate 可以为任意值,count 为 0;

我们遍历数组 nums 中的所有元素,对于每个元素 x,在判断 x 之前,如果 count 的值为 0,我们先将 x 的值赋予 candidate,随后我们判断 x:

   如果 x 与 candidate 相等,那么计数器 count 的值增加 1;

   如果 x 与 candidate 不等,那么计数器 count 的值减少 1

在遍历完成后,candidate 即为整个数组的众数。

我们举一个具体的例子,例如下面的这个数组:

[7, 7, 5, 7, 5, 1 | 5, 7 | 5, 5, 7, 7 | 7, 7, 7, 7]

在遍历到数组中的第一个元素以及每个在 | 之后的元素时,candidate 都会因为 count 的值变为 0 而发生改变。最后一次 candidate 的值从 5 变为 7,也就是这个数组中的众数。

Boyer-Moore 算法的正确性较难证明,这里给出一种较为详细的用例子辅助证明的思路,供读者参考:

首先我们根据算法步骤中对 count 的定义,可以发现:在对整个数组进行遍历的过程中,count 的值一定非负。这是因为如果 count 的值为 0,那么在这一轮遍历的开始时刻,我们会将 x 的值赋予 candidate 并在接下来的一步中将 count 的值增加 1。因此 count 的值在遍历的过程中一直保持非负。

那么 count 本身除了计数器之外,还有什么更深层次的意义呢?我们还是以数组

[7, 7, 5, 7, 5, 1 | 5, 7 | 5, 5, 7, 7 | 7, 7, 7, 7]

 作为例子,首先写下它在每一步遍历时 candidate 和 count 的值:

nums: [7, 7, 5, 7, 5, 1 | 5, 7 | 5, 5, 7, 7 | 7, 7, 7, 7]

candidate: 7 7 7 7 7 7 5 5 5 5 5 5 7 7 7 7

count: 1 2 1 2 1 0 1 0 1 2 1 0 1 2 3 4

 我们再定义一个变量 value,它和真正的众数 maj 绑定。在每一步遍历时,如果当前的数 x 和 maj 相等,那么 value 的值加 1,否则减 1。value 的实际意义即为:到当前的这一步遍历为止,众数出现的次数比非众数多出了多少次。我们将 value 的值也写在下方:

nums: [7, 7, 5, 7, 5, 1 | 5, 7 | 5, 5, 7, 7 | 7, 7, 7, 7] value: 1 2 1 2 1 0 -1 0 -1 -2 -1 0 1 2 3 4

 我们将 count 和 value 放在一起:

nums: [7, 7, 5, 7, 5, 1 | 5, 7 | 5, 5, 7, 7 | 7, 7, 7, 7] count: 1 2 1 2 1 0 1 0 1 2 1 0 1 2 3 4 value: 1 2 1 2 1 0 -1 0 -1 -2 -1 0 1 2 3 4

 发现在每一步遍历中,count 和 value 要么相等,要么互为相反数!

并且在候选众数 candidate 就是 maj 时,它们相等,candidate 是其它的数时,它们互为相反数!

为什么会有这么奇妙的性质呢?这并不难证明:我们将候选众数 candidate 保持不变的连续的遍历称为「一段」。

在同一段中,count 的值是根据 candidate == x 的判断进行加减的。

那么如果 candidate 恰好为 maj,那么在这一段中,count 和 value 的变化是同步的;

如果 candidate 不为 maj,那么在这一段中 count 和 value 的变化是相反的。因此就有了这样一个奇妙的性质。

这样以来,由于:

我们证明了 count 的值一直为非负,在最后一步遍历结束后也是如此;

由于 value 的值与真正的众数 maj 绑定,并且它表示「众数出现的次数比非众数多出了多少次」,那么在最后一步遍历结束后,value 的值为正数;

在最后一步遍历结束后,count 非负,value 为正数,所以它们不可能互为相反数,只可能相等,即 count == value。因此在最后「一段」中,count 的 value 的变化是同步的,也就是说,candidate 中存储的候选众数就是真正的众数 maj。

 实现代码:

class Solution {public int majorityElement(int[] nums) {int count = 0;Integer candidate = null;for (int num : nums) {//遍历数组if (count == 0) {candidate = num;}count += (num == candidate) ? 1 : -1;}return candidate;}
}

复杂度分析

时间复杂度:O(n)。

Boyer-Moore 算法只对数组进行了一次遍历。

空间复杂度:O(1)。

Boyer-Moore 算法只需要常数级别的额外空间。

通过率100% 的解法
class Solution {public int majorityElement(int[] nums) {return f(nums, 0);//从数组的第一个元素查找多数元素}public int f(int[] nums, int m){int c = nums[m], count = 1, i = m;//c记录当前位置的值  count当前元素比其他元素多出现的次数  i循环遍历索引int n = nums.length;//n数组长度while(i < n - 1 && count > 0){i++;if(nums[i] == c){count++;}else{count--;}}if(i == n - 1) return c;//到数组末尾时,count还是大于0,说明该元素出现的次数比其他元素都多else return f(nums, i + 1);//否则,看下一个元素}
}

欢迎大家点赞,评论加关注呦 

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

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

相关文章

android studio 读写文件操作(应用场景二)

android studio版本&#xff1a;2023.3.1 patch2 例程&#xff1a;readtextviewIDsaveandread 本例程是个过渡例程&#xff0c;如果单是实现下图的目的有更简单的方法&#xff0c;但这个方法是下一步工作的基础&#xff0c;所以一定要做。 例程功能&#xff1a;将两个textvi…

【NLP 9、实践 ① 五维随机向量交叉熵多分类】

目录 五维向量交叉熵多分类 规律&#xff1a; 实现&#xff1a; 1.设计模型 2.生成数据集 3.模型测试 4.模型训练 5.对训练的模型进行验证 调用模型 你的平静&#xff0c;是你最强的力量 —— 24.12.6 五维向量交叉熵多分类 规律&#xff1a; x是一个五维(索引)向量&#xff…

windows文件下换行, linux上不换行 解决CR换行符替换为LF notepad++

html文件是用回车换行的&#xff0c;在windows电脑上&#xff0c;显示正常。 文件上传到linux服务器后&#xff0c;文件不换行了。只有一行。而且相关js插件也没法正常运行。 用notepad查看&#xff0c;显示尾部换行符&#xff0c;是CR&#xff0c;这就是原因。CR是不被识别的。…

Unity 模拟百度地图,使用鼠标控制图片在固定区域内放大、缩小、鼠标左键拖拽移动图片

效果展示&#xff1a; 步骤流程&#xff1a; 1.使用的是UGUI&#xff0c;将下面的脚本拖拽到图片上即可。 using UnityEngine; using UnityEngine.UI; using UnityEngine.EventSystems;public class CheckImage : MonoBehaviour, IDragHandler, IBeginDragHandler, IEndDragH…

游戏引擎学习第30天

仓库: https://gitee.com/mrxiao_com/2d_game 回顾 在这段讨论中&#xff0c;重点是对开发过程中出现的游戏代码进行梳理和进一步优化的过程。 工作回顾&#xff1a;在第30天&#xff0c;回顾了前一天的工作&#xff0c;并提到今天的任务是继续从第29天的代码开始&#xff0c…

基于MFC绘制门电路

MFC绘制门电路 1. 设计内容、方法与难点 本课题设计的内容包括了基本门电路中与门和非门的绘制、选中以及它们之间的连接。具体采用的方法是在OnDraw函数里面进行绘制&#xff0c;并设计元器件基类&#xff0c;派生出与门和非门&#xff0c;并组合了一个引脚类&#xff0c;在…

【text2sql】低资源场景下Text2SQL方法

SFT使模型能够遵循输入指令并根据预定义模板进行思考和响应。如上图&#xff0c;、 和 是用于通知模型在推理过程中响应角色的角色标签。 后面的内容表示模型需要遵循的指令&#xff0c;而 后面的内容传达了当前用户对模型的需求。 后面的内容代表模型的预期输出&#xff0c;也…

学习threejs,实现配合使用WebWorker

&#x1f468;‍⚕️ 主页&#xff1a; gis分享者 &#x1f468;‍⚕️ 感谢各位大佬 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍⚕️ 收录于专栏&#xff1a;threejs gis工程师 文章目录 一、&#x1f340;前言1.1 ☘️WebWorker web端多线程 二、…

16-03、JVM系列之:内存与垃圾回收篇(三)

JVM系列之&#xff1a;内存与垃圾回收篇(三) ##本篇内容概述&#xff1a; 1、执行引擎 2、StringTable 3、垃圾回收一、执行引擎 ##一、执行引擎概述 如果想让一个java程序运行起来&#xff0c;执行引擎的任务就是将字节码指令解释/编译为对应平台上的本地机器指令才可以。 简…

小程序 - 美食列表

小程序交互练习 - 美食列表小程序开发笔记 目录 美食列表 功能描述 准备工作 创建项目 配置页面 配置导航栏 启动本地服务器 页面初始数据 设置获取美食数据 设置onload函数 设置项目配置 页面渲染 页面样式 处理电话格式 创建处理电话格式脚本 页面引入脚本 …

Qt6.8 QGraphicsView鼠标坐标点偏差

ui文件拖放QGraphicsView&#xff0c;src文件定义QGraphicsScene赋值给图形视图。 this->scene new QGraphicsScene();ui.graph->setScene(this->scene);对graphicview过滤事件&#xff0c;只能在其viewport之后安装&#xff0c;否则不响应。 ui.graph->viewport…

若依 ruoyi VUE el-select 直接获取 选择option 的 label和value

1、最新在研究若依这个项目&#xff0c;我使用的是前后端分离的方案&#xff0c;RuoYi-Vue-fast(后端) RuoYi-Vue-->ruoyi-ui(前端)。RuoYi-Vue-fast是单应用版本没有区分那么多的modules 自己开发起来很方便&#xff0c;这个项目运行起来很方便&#xff0c;但是需要自定义的…

springboot事务手动回滚报错

捕捉异常之后手动标记回滚事务 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); 没有嵌套事务&#xff0c;还是报Transaction rolled back because it has been marked as rollback-only异常错误 查看错误堆栈&#xff0c;service调用的方法外层还套…

使用 LlamaFactory 结合开源大语言模型实现文本分类:从数据集构建到 LoRA 微调与推理评估

文章目录 背景介绍文本分类数据集Lora 微调模型部署与推理期待模型的输出结果 文本分类评估代码 背景介绍 本文将一步一步地&#xff0c;介绍如何使用llamafactory框架利用开源大语言模型完成文本分类的实验&#xff0c;以 LoRA微调 qwen/Qwen2.5-7B-Instruct 为例。 文本分类…

ARM内核与单片机

1.单片机硬件架构如下所示&#xff1a;各种硬件通过总线进行连接。 2.M4内核架构 3.单片机如何工作&#xff1a; 4.CPU是通过读写寄存器来控制GPIO的 5.GPIO的硬件框架&#xff1a;一共有8种模式 &#xff08;1&#xff09;推挽/推挽复用输出。下图先看图1&#xff0c;如果输入…

PHP 命令执行漏洞学习记录

PHP 命令执行 命令函数 作用 例子 system() 执行外部程序,并且显示输出 system(whoami) exec() 执行一个外部程序 echo exec(whoami); shell_exec() 通过shell环境执行命令,并且将完整的输出以字符串的形式返回 echo shell_exec(whoami); passthru() 执行外部程序…

VSCode GDB远程嵌入开发板调试

VSCode GDB远程嵌入式开发板调试 一、原理 嵌入式系统中一般在 PC端运行 gdb工具&#xff0c;源码也是在 PC端&#xff0c;源码对应的可执行文件放到开发板中运行。为此我们需要在开发板中运行 gdbserver&#xff0c;通过网络与 PC端的 gdb进行通信。因此要想在 PC上通过 gdb…

专业140+总分420+上海交通大学819考研经验上交电子信息与通信工程,真题,大纲,参考书。博睿泽信息通信考研论坛,信息通信考研Jenny

考研结束&#xff0c;专业819信号系统与信号处理140&#xff0c;总分420&#xff0c;终于梦圆交大&#xff0c;高考时敢都不敢想目标&#xff0c;现在已经成为现实&#xff0c;考研后劲很大&#xff0c;这一年的复习经历&#xff0c;还是历历在目&#xff0c;整理一下&#xff…

【NLP修炼系列之Bert】Bert多分类多标签文本分类实战(附源码下载)

引言 今天我们就要用Bert做项目实战&#xff0c;实现文本多分类任务和我在实际公司业务中的多标签文本分类任务。通过本篇文章&#xff0c;可以让想实际入手Bert的NLP学习者迅速上手Bert实战项目。 1 项目介绍 本文是Bert文本多分类和多标签文本分类实战&#xff0c;其中多分…

[Redis#17] 主从复制 | 拓扑结构 | 复制原理 | 数据同步 | psync

目录 主从模式 主从复制作用 建立主从复制 主节点信息 从节点信息 断开主从复制关系 主从拓扑结构 主从复制原理 1. 复制过程 2. 数据同步&#xff08;PSYNC&#xff09; 3. 三种复制方式 一、全量复制 二、部分复制 三、实时复制 四、主从复制模式存在的问题 在…