分治——归并排序算法

例题一

解法(归并排序):
算法思路:
归并排序的流程充分的体现了「分⽽治之」的思想,⼤体过程分为两步:
分:将数组⼀分为⼆为两部分,⼀直分解到数组的⻓度为 1 ,使整个数组的排序过程被分为
「左半部分排序」 + 「右半部分排序」;
治:将两个较短的「有序数组合并成⼀个⻓的有序数组」,⼀直合并到最初的⻓度。 

例题二

解法(利⽤归并排序的过程 --- 分治):
算法思路:
⽤归并排序求逆序数是很经典的⽅法,主要就是在归并排序的合并过程中统计出逆序对的数量,也
就是在合并两个有序序列的过程中,能够快速求出逆序对的数量。
我们将这个问题分解成⼏个⼩问题,逐⼀破解这道题。
注意:默认都是升序,如果掌握升序的话,降序的归并过程也是可以解决问题的。
先解决第⼀个问题,为什么可以利⽤归并排序?
如果我们将数组从中间划分成两个部分,那么我们可以将逆序对产⽣的⽅式划分成三组:
逆序对中两个元素:全部从左数组中选择
逆序对中两个元素:全部从右数组中选择
逆序对中两个元素:⼀个选左数组另⼀个选右数组
根据排列组合的分类相加原理,三种种情况下产⽣的逆序对的总和,正好等于总的逆序对数量。
⽽这个思路正好匹配归并排序的过程:
先排序左数组;
再排序右数组;
左数组和右数组合⼆为⼀。
因此,我们可以利⽤归并排序的过程,先求出左半数组中逆序对的数量,再求出右半数组中逆序对的数量,最后求出⼀个选择左边,另⼀个选择右边情况下逆序对的数量,三者相加即可。
解决第⼆个问题,为什么要这么做?
在归并排序合并的过程中,我们得到的是两个有序的数组。我们是可以利⽤数组的有序性,快速统计出逆序对的数量,⽽不是将所有情况都枚举出来。
最核⼼的问题,如何在合并两个有序数组的过程中,统计出逆序对的数量?
合并两个有序序列时求逆序对的⽅法有两种:
1. 快速统计出某个数前⾯有多少个数⽐它⼤;
2. 快速统计出某个数后⾯有多少个数⽐它⼩;
⽅法⼀:快速统计出某个数前⾯有多少个数⽐它⼤
通过⼀个⽰例来演⽰⽅法⼀:
假定已经有两个已经有序的序列以及辅助数组 left = [5, 7, 9] right = [4, 5, 8] help = [ ],通过合并两
个有序数组的过程,来求得逆序对的数量:
规定如下定义来叙述过程:
cur1 遍历 left 数组,cur2 遍历 right 数组
ret 记录逆序对的数量
第⼀轮循环:
left[cur1] > right[cur2],由于两个数组都是升序的,那么我们可以断定,此刻 left 数组中 [cur1, 2] 区间内的 3 个元素均可与 right[cur2] 的元素构成逆序对,因此可以累加逆序对的数量 ret += 3,并且将 right[cur2] 加⼊到辅助数组中,cur2++ 遍历下⼀个元素。
第⼀轮循环结束后:left = [5, 7, 9] right = [x, 5, 8] help = [4] ret = 3 cur1 = 0 cur2 = 1
第⼆轮循环: left[cur1] == right[cur2],因为 right[cur2] 可能与 left 数组中往后的元素构成逆序对,因此我 们需要将 left[cur1] 加⼊到辅助数组中去,此时没有产⽣逆序对,不更新 ret。
第⼆轮循环结束后:left = [x, 7, 9] right = [x, 5, 8] help = [4, 5] ret = 3 cur1 = 1 cur2 = 1
第三轮循环: left[cur1] > right[cur2],与第⼀轮循环相同,此刻 left 数组中[cur1, 2] 区间内的 2 个元素均可 与 right[cur2] 的元素构成逆序对,更新 ret 的值为 ret += 2,并且将 right[cur2] 加⼊到辅助数组中 去,cur2++ 遍历下⼀个元素。
第三轮循环结束后:left = [x, 7, 9] right = [x, x, 8] help = [4, 5, 5] ret = 5 cur1 = 1 cur2 = 2
第四轮循环: left[cur1] < right[cur2],由于两个数组都是升序的,因此我们可以确定 left[cur1] ⽐ right 数组中的所有元素都要⼩。left[cur1] 这个元素是不可能与 right 数组中的元素构成逆序对。因此,⼤胆的将 left[cur1] 这个元素加⼊到辅助数组中去,不更细 ret 的值。
第四轮循环结束后:left = [x, x, 9] right = [x, x, 8] help = [4, 5, 5, 7] ret = 5 cur1 = 2 cur2 = 2
第五轮循环:left[cur1] > right[cur2],与第⼀、第三轮循环相同。此时 left 数组内的 1 个元素能与
right[cur2] 构成逆序对,更新 ret 的值,并且将 right[cur2] 加⼊到辅助数组中去。
第五轮循环结束后:left = [x, x, 9] right = [x, x, x] help = [4, 5, 5, 7, 8] ret = 6 cur1 = 2 cur2 = 2
处理剩余元素:
如果是左边出现剩余,说明左边剩下的所有元素都是⽐右边元素⼤的,但是它们都是已经被计算过的(我们以右边的元素为基准的),因此不会产⽣逆序对,仅需归并排序即可。
如果是右边出现剩余,说明右边剩下的元素都是⽐左边⼤的,不符合逆序对的定义,因此也不需要 处理,仅需归并排序即可。
整个过程只需将两个数组遍历⼀遍即可,时间复杂度为 O(N)。
由上述过程我们可以得出⽅法⼀统计逆序对的关键点:
在合并有序数组的时候,遇到左数组当前元素 > 右数组当前元素时,我们可以通过计算左数组中剩余元素的⻓度,就可快速求出右数组当前元素前⾯有多少个数⽐它⼤,对⽐解法⼀中⼀个⼀个枚举逆序对效率快了许多。
⽅法⼆:快速统计出某个数后⾯有多少个数⽐它⼩
依旧通过⼀个⽰例来演⽰⽅法⼆:
假定已经有两个已经有序的序列以及辅助数组 left = [5, 7, 9] right = [4, 5, 8] help = [ ],通过合并两
个有序数组的过程,来求得逆序对的数量:
规定如下定义来叙述过程:
cur1 遍历 left 数组,cur2 遍历 right 数组 ret 记录逆序对的数量
第⼀轮循环: left[cur1] > right[cur2],先不要着急统计,因为我们要找的是当前元素后⾯有多少⽐它⼩的,这⾥虽然出现了⼀个,但是 right 数组中依旧还可能有其余⽐它⼩的。因此此时仅将 right[cur2] 加⼊到辅助数组中去,并且将 cur2++。
第⼀轮循环结束后:left = [5, 7, 9] right = [x, 5, 8] help = [4] ret = 0 cur1 = 0 cur2 = 1 第⼆轮循环:
left[cur1] == right[cur2],由于两个数组都是升序,这个时候对于元素 left[cur1] 来说,我们已
经可以断定 right 数组中 [0, cur2) 左闭右开区间上的元素都是⽐它⼩的。因此此时可以统计逆序对的数量 ret += cur2 - 0,并且将 left[cur1] 放⼊到辅助数组中去,cur1++ 遍历下⼀个元素。
第⼆轮循环结束后:left = [x, 7, 9] right = [x, 5, 8] help = [4, 5] ret = 1 cur1 = 1 cur2 = 1
第三轮循环:left[cur1] > right[cur2],与第⼀轮循环相同,直接将 right[cur2] 加⼊到辅助数组中去,cur2++ 遍历下⼀个元素。
第三轮循环结束后:left = [x, 7, 9] right = [x, x, 8] help = [4, 5, 5] ret = 1 cur1 = 1 cur2 = 2
第四轮循环:left[cur1] < right[cur2],由于两个数组都是升序的,这个时候对于元素 left[cur1] 来说,我们依旧已经可以断定 right 数组中 [0, cur2) 左闭右开区间上的元素都是⽐它⼩的。因此此时可以统计逆序对的数量 ret += cur2 - 0,并且将 left[cur1] 放⼊到辅助数组中去,cur1++ 遍历下⼀个元素。
第四轮循环结束后:left = [9] right = [8] help = [4, 5, 5, 7] ret = 3 cur1 = 2 cur2 = 2
第五轮循环:left[cur1] > right[cur2],与第⼀、第三轮循环相同。直接将 right[cur2] 加⼊到辅助数组中去,cur2++ 遍历下⼀个元素。
第五轮循环结束后:left = [x, x, 9] right = [x, x, x] help = [4, 5, 5, 7, 8] ret = 3 cur1 = 2 cur2 = 2
处理剩余元素:
如果是左边出现剩余,说明左边剩下的所有元素都是⽐右边元素⼤的,但是相⽐较于⽅法⼀,逆序对的数量是没有统计过的。因此,我们需要统计 ret 的值:
设左边数组剩余元素的个数为 leave
ret += leave * (cur2 - 0) 对于本题来说,处理剩余元素的时候, left 数组剩余 1 个元素,cur2 - 0 = 3,因此 ret 需要类加上 3,结果为 6。与⽅法⼀求得的结果相同。
如果是右边出现剩余,说明右边剩下的元素都是⽐左边⼤的,不符合逆序对的定义,因此也不需要处理,仅需归并排序即可。整个过程只需将两个数组遍历⼀遍即可,时间复杂度依旧为 O(N)。
由上述过程我们可以得出⽅法⼆统计逆序对的关键点:
在合并有序数组的时候,遇到左数组当前元素 <= 右数组当前元素时,我们可以通过计算右数组已经遍历过的元素的⻓度,快速求出左数组当前元素后⾯有多少个数⽐它⼤。
但是需要注意的是,在处理剩余元素的时候,⽅法⼆还需要统计逆序对的数量。

例题三

3. 解法(归并排序):
算法思路: 这⼀道题的解法与 求数组中的逆序对 的解法是类似的,但是这⼀道题要求的不是求总的个数,⽽是要返回⼀个数组,记录每⼀个元素的右边有多少个元素⽐⾃⼰⼩。
但是在我们归并排序的过程中,元素的下标是会跟着变化的,因此我们需要⼀个辅助数组,来将数
组元素和对应的下标绑定在⼀起归并,也就是再归并元素的时候,顺势将下标也转移到对应的位置
上。由于我们要快速统计出某⼀个元素后⾯有多少个⽐它⼩的,因此我们可以利⽤求逆序对的第⼆种⽅法。
算法流程:
创建两个全局的数组:
vector<int> index:记录下标
vector<int> ret:记录结果
index ⽤来与原数组中对应位置的元素绑定,ret ⽤来记录每个位置统计出来的逆序对的个数。
countSmaller() 主函数:
a. 计算 nums 数组的⼤⼩为 n;
b. 初始化定义的两个全局的数组;
i. 为两个数组开辟⼤⼩为 n 的空间
ii. index 初始化为数组下标;
iii. ret 初始化为 0
c. 调⽤ mergeSort() 函数,并且返回 ret 结果数组。
void mergeSort( vector<int>& nums, int left, int right ) 函数:
函数设计:通过修改全局的数组 ret, 统计出每⼀个位置对应的逆序对的数量,并且排序;
⽆需返回值,因为直接对全局变量修改,当函数结束的时候,全局变量已经被修改成最后的结果。
mergeSort() 函数流程:
a. 定义递归出⼝:left >= right 时,直接返回;
b. 划分区间:根据中点 mid,将区间划分为 [left, mid] 和 [mid + 1, right];
c. 统计左右两个区间逆序对的数量:
i. 统计左边区间 [left, mid] 中每个元素对应的逆序对的数量到 ret 数组中,并排序;
ii. 统计右边区间 [mid + 1, right] 中每个元素对应的逆序对的数量到 ret 数组中,并排序。 d. 合并左右两个有序区间,并且统计出逆序对的数量:
i. 创建两个⼤⼩为 right - left + 1 ⼤⼩的辅助数组:
numsTmp: 排序⽤的辅助数组;
indexTmp:处理下标⽤的辅助数组。
ii. 初始化遍历数组的指针:cur1 = left(遍历左半部分数组)cur2 = mid + 1(遍历右半边数
组)dest = 0(遍历辅助数组)curRet(记录合并时产⽣的逆序对的数量);
iii. 循环合并区间:
当 nums[cur1] <= nums[cur2] 时:
说明此时 [mid + 1, cur2) 之间的元素都是⼩于 nums[cur1] 的,需要累加到 ret 数组的 indext[cur1] 位置上(因为 index 存储的是元素对应位置在原数组中的下标)
归并排序:不仅要将数据放在对应的位置上,也要将数据对应的坐标也放在对应的位
置上,使数据与原始的下标绑定在⼀起移动。
当 nums[cur1] > nums[cur2] 时,⽆需统计,直接归并,注意 index 也要跟着归并。
iv. 处理归并排序中剩余的元素;
当左边有剩余的时候,还需要统计逆序对的数量;
当右边还有剩余的时候,⽆需统计,直接归并。
v. 将辅助数组的内容替换到原数组中去;

例题四

解法(归并排序):
算法思路:
⼤思路与求逆序对的思路⼀样,就是利⽤归并排序的思想,将求整个数组的翻转对的数量,转换成
三部分:左半区间翻转对的数量,右半区间翻转对的数量,⼀左⼀右选择时翻转对的数量。重点就
是在合并区间过程中,如何计算出翻转对的数量。
与上个问题不同的是,上⼀道题我们可以⼀边合并⼀遍计算,但是这道题要求的是左边元素⼤于右
边元素的两倍,如果我们直接合并的话,是⽆法快速计算出翻转对的数量的。
例如 left = [4, 5, 6] right = [3, 4, 5] 时,如果是归并排序的话,我们需要计算 left 数组中有多少个
能与 3 组成翻转对。但是我们要遍历到最后⼀个元素 6 才能确定,时间复杂度较⾼。 因此我们需要在归并排序之前完成翻转对的统计。
下⾯依旧以⼀个⽰例来模仿两个有序序列如何快速求出翻转对的过程:
假定已经有两个已经有序的序列 left = [4, 5, 6] right = [1, 2, 3] 。⽤两个指针 cur1 cur2 遍历两组。
对于任意给定的 left[cur1] ⽽⾔,我们不断地向右移动 cur2,直到 left[cur1] <= 2 * right[cur2]。此时对于 right 数组⽽⾔,cur2 之前的元素全部都可以与 left[cur1] 构成翻转对。
随后,我们再将 cur1 向右移动⼀个单位,此时 cur2 指针并不需要回退(因为 left 数组是升序
的)依旧往右移动直到 left[cur1] <= 2 * right[cur2]。不断重复这样的过程,就能够求出所有左右端点分别位于两个⼦数组的翻转对数⽬。
由于两个指针最后都是不回退的的扫描到数组的结尾,因此两个有序序列求出翻转对的时间复杂度
是 O(N)。综上所述,我们可以利⽤归并排序的过程,将求⼀个数组的翻转对转换成求 左数组的翻转对数量 + 右数组中翻转对的数量 + 左右数组合并时翻转对的数量。

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

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

相关文章

网络游戏推广利器:Xinstall一站式解决方案

随着网络游戏的飞速发展&#xff0c;游戏推广已成为游戏公司不可或缺的一环。然而&#xff0c;面对渠道繁多、效果评估困难、用户获取成本高昂等问题&#xff0c;许多游戏公司在推广过程中感到力不从心。今天&#xff0c;我们要为大家介绍一款强大的游戏推广利器——Xinstall。…

[leetcode] 100. 相同的树

给你两棵二叉树的根节点 p 和 q &#xff0c;编写一个函数来检验这两棵树是否相同。 如果两个树在结构上相同&#xff0c;并且节点具有相同的值&#xff0c;则认为它们是相同的。 示例 1&#xff1a; 输入&#xff1a;p [1,2,3], q [1,2,3] 输出&#xff1a;true示例 2&a…

Linux 理解进程信号

目录 一、共享内存通信机制中的临界资源访问与同步控制 1、概念 2、生活角度理解信号机制 3、信号量的操作 二、信号 1、生活角度的信号 2、技术应用角度的信号 3、操作系统角度的信号 信号如何产生 理解组合键变为信号 理解信号如何被进程保存 时钟中断&#xff0…

基于《2023腾讯云容器和函数计算技术实践精选集》—探索腾讯云TKE的Docker容器、Serverless和微服务优势

重剑无锋&#xff0c;大巧不工。 ——金庸 腾讯云TKE&#xff0c;全称Tencent Kubernetes Engine&#xff0c;是一种完全托管式的容器服务。它可以帮助用户快速、高效地部署和管理Kubernetes集群&#xff0c;并提供一系列与之相关的云服务&#xff0c;如负载均衡、云硬盘、对象…

汇编语言第四版-第3章 寄存器(内访问)

al为ax的低字节&#xff0c;ax寄存器为累加器。

Vue element-plus 导航栏 [el-menu]

导航栏 [el-menu] Menu 菜单 | Element Plus el-menu有很多属性和子标签&#xff0c;为网站提供导航功能的菜单。 常用标签&#xff1a; 它里面有两个子标签。el-menu-item&#xff0c;它其实就是el-menu每一个里面的item&#xff0c;item就是真实匹配到路由的每个栏目&#…

Unity3d使用Jenkins自动化打包(Windows)(一)

文章目录 前言一、安装JDK二、安装Jenkins三、Jenkins插件安装和使用基础操作 实战一基础操作 实战二 四、离线安装总结 前言 本篇旨在介绍基础的安装和操作流程&#xff0c;只需完成一次即可。后面的篇章将深入探讨如何利用Jenkins为Unity项目进行打包。 一、安装JDK 1、进入…

机器学习优化算法(深度学习)

目录 预备知识 梯度 Hessian 矩阵&#xff08;海森矩阵&#xff0c;或者黑塞矩阵&#xff09; 拉格朗日中值定理 柯西中值定理 泰勒公式 黑塞矩阵&#xff08;Hessian矩阵&#xff09; Jacobi 矩阵 优化方法 梯度下降法&#xff08;Gradient Descent&#xff09; 随机…

Python问题列表

文章目录 1、使用pip安装的模块都存放到哪里了&#xff1f;2、安装fitz包报错&#xff0c;如何解决&#xff1f;3、python代码运行时&#xff0c;控制台输出乱码如何解决。4、vscode中第三方库不自动补齐 1、使用pip安装的模块都存放到哪里了&#xff1f; 答&#xff1a; pip是…

【OpenGL】使用 python + Qt + OpenGL 的现代渲染

伴随资源 目录 一、说明二、 关于PyQt6.x2.1 QOpenGLWidget详细说明2.2 绘画技巧 三、PyOpenGL四、OpenGL 管线五、Python集成开发环境5.1 Emacs配置5.2 pycharm环境 六、你好&#xff0c;OpenGL&#xff01;七、QGL控件八、平截头体.svg九、定义几何9.1 立即模式与保留模式9…

如何在Portainer中创建Nginx服务并搭建静态站点实现公网访问本地网站

文章目录 前言1. 安装Portainer1.1 访问Portainer Web界面 2. 使用Portainer创建Nginx容器3. 将Web静态站点实现公网访问4. 配置Web站点公网访问地址4.1公网访问Web站点 5. 固定Web静态站点公网地址6. 固定公网地址访问Web静态站点 前言 Portainer是一个开源的Docker轻量级可视…

ES学习日记(一)-------单节点安装启动

基于ES7.4.1编写,其实一开始用的最新的8.1,但是问题太多了!!!!不稳定,降到7.4 下载好的安装包上传到服务器或虚拟机,创建ES目录,命令mkdir -p /路径xxxx 复制安装包到指定路径并解压: tar zxvf elasticsearch-8.1.0-linux-x86_64.tar.gz -C /usr/local/es/ 进入bin目录安装,命…

JAVA学习笔记21(访问修饰符)

1.访问修饰符 ​ *基本介绍 ​ java提供四种访问控制修饰符号&#xff0c;用于控制方法和属性(成员变量)的访问权限(范围) 1.公开级别&#xff1a;用public修饰&#xff0c;对外公开 2.受保护级别&#xff1a;用protected修饰&#xff0c;对子类和同一个包中的类公开 3.默…

Linux基本指令篇

在前边&#xff0c;我们已经了解过了Linux操作系统的发展和应用&#xff0c;从该篇起&#xff0c;就正式进入对Linux的学习。 今天我们就来在Xshell上远程登录我们的云服务器。首先我们要知道自己云服务器的公网ip&#xff0c;然后修改一下密码。 点击跳转 修改完密码之后我们…

java题目15:从键盘输入n个数,求这n个数中的最大数与最小数并输出(MaxAndMin15)

每日小语 你是否有资格摆脱身上的枷锁呢&#xff1f;有许多人一旦获得解放&#xff0c;他的最后一点价值也就会跟着丧失。 ——尼采 自己敲写 它不按我想的来。。。 //从键盘输入n个数&#xff0c;求这n个数中的最大数与最小数并输出 import java.util.Scanner; public clas…

2024年美团笔试题(1)

一.题目描述 小美拿到了一个排列&#xff0c;其中初始所有元素都是红色&#xff0c;但有些元素被染成了白色。 小美每次操作可以选择交换任意两个红色元素的位置。她希望操作尽可能少的次数使得数组变成非降序&#xff0c;你能帮帮她吗? 排列是指:一个长度为n的数组&#…

【跟着CHATGPT学习硬件外设 | 02】GPIO

文章目录 &#x1f680; 概念揭秘快速入门关键精华 &#x1f31f; 秒懂案例生活类比实战演练步骤1&#xff1a;硬件配置步骤2&#xff1a;软件配置步骤3&#xff1a;发送和接收数据步骤4&#xff1a;处理异常步骤5&#xff1a;优化操作手册硬件设计注意事项配置攻略准备阶段配置…

镭速如何解决UDP传输不通的问题

我们之前有谈到过企业如果遇到UDP传输不通的情况&#xff0c;常见的一些解决方式&#xff0c;同时也介绍了一站式企业文件传输方式-镭速相关优势&#xff0c;如果在实际应用中&#xff0c;若镭速UDP传输出现不通的情况&#xff0c;需要按照网络通信的一般性排查方法以及针对镭速…

Git--08--Git分支合并操作

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 Git分支合并操作案例流程客户端&#xff1a;GitExtensions操作步骤&#xff1a;A操作步骤&#xff1a;B操作步骤&#xff1a;C操作步骤&#xff1a;D操作步骤&#…

SOC内部集成网络MAC外设+ PHY网络芯片方案:MII/RMII 接口与 MDIO 接口

一. 简介 本文来了解一下常用的一种网络硬件方案&#xff1a;SOC内部集成网络MAC外设 PHY网络芯片方案。 其中涉及的 MII接口&#xff0c;RMII接口&#xff08;MII接口与RMII接口二选一&#xff09;&#xff0c;MDIO接口&#xff0c;RJ45。 二. MII/RMII 接口&#xff0c;M…