【算法专题】归并排序

目录

1. 排序数组

2. 交易逆序对的总数

3. 计算右侧小于当前元素的个数

4. 翻转对

总结


1. 排序数组

912. 排序数组 - 力扣(LeetCode)

        今天我们使用归并排序来对数组进行排序,实际上,归并排序和快速排序是有一定相似之处的,都运用了分而治之的思想提升了排序效率。快速排序的实现思路是每次排序把区间划分为小于基准元素、等于基准元素、大于基准元素三个部分,直至数组整体有序为止;而归并排序的实现思路则是每次排序把区间平均划分为两个部分,分别对这两个部分再次排序,然后把这两个部分合并,重复这个过程直至子数组为一。

        显然合并数组这个操作是需要一个数组进行辅助的,由于归并排序过程中两个相等的元素在数组中的位置不会发生改变,所以这是一个稳定的排序算法,虽然在不要求稳定的情况下,都是快速排序比归并排序更快,但归并排序也有自己的应用场景,这点我们在后面会提到。

       

class Solution {
public:vector<int> temp;vector<int> sortArray(vector<int>& nums) {temp.resize(nums.size());mergesort(nums, 0, nums.size() - 1);return nums;}void mergesort(vector<int> &nums, int left, int right){if(left >= right) return;int mid = (left + right) >> 1;mergesort(nums, left, mid);mergesort(nums, mid + 1, right);int cur1= left, cur2 = mid + 1, i = 0;while(cur1 <= mid && cur2 <= right){temp[i++] = (nums[cur1] <= nums[cur2]) ? nums[cur1++] : nums[cur2++];}while(cur1 <= mid) temp[i++] = nums[cur1++];while(cur2 <= right) temp[i++] = nums[cur2++];for(int j = left; j <= right; j++){nums[j] = temp[j - left];}}
};

2. 交易逆序对的总数

LCR 170. 交易逆序对的总数 - 力扣(LeetCode)

        依据题意,我们需要求出一个数组中的逆序对总数,逆序对的定义是前面的数大于后面的数时,这两个数可以组成逆序对。首先能想到的肯定是暴力枚举,两层for循环列举出所有符合条件的逆序对情况,但既然这是困难题,暴力枚举法肯定是通过不了的,所以我们要想办法对暴力法做出优化。

        首先,如果我们把数组平均分为左右两个部分,那么要查找逆序对的步骤就是在左半部分找逆序对、在右半部分找逆序对、左右部分各取一个数,找逆序对。这样一来,就能找出所有满足条件的逆序对了,这时大家可能就会奇怪了,这不还是相当于枚举吗?确实是这样,但如果我们在找完左半部分逆序对后对左边进行排序、找完右半部分逆序对后对右边进行排序、在找完左右部分的逆序对后对数组整体进行排序,大家可能发现了,这样一来我们就能够用归并排序来对求逆序对的流程进行优化了。

        为什么说排序能够优化查找逆序对的效率呢?我举个例子大家就明白了。

大家可以发现,当nums[cur1] > nums[cur2]时,我们就一次性找到了mid-cur1+1个符合条件的逆序对!和暴力枚举法比起来,大大提升了效率!

class Solution {
public:vector<int> temp;int reversePairs(vector<int>& record) {temp.resize(record.size());return mergesort(record, 0, record.size() - 1);}int mergesort(vector<int> &nums, int left, int right){if(left >= right) return 0;int ret = 0;int mid = (left + right) >> 1;ret += mergesort(nums, left, mid);ret += mergesort(nums, mid + 1, right);int cur1 = left, cur2 = mid + 1, i = 0;while(cur1 <= mid && cur2 <= right){if(nums[cur1] <= nums[cur2]){temp[i++] = nums[cur1++];}else{ret += mid - cur1 + 1;temp[i++] = nums[cur2++];}}while(cur1 <= mid) temp[i++] = nums[cur1++];while(cur2 <= right) temp[i++] = nums[cur2++];for(int j = left; j <= right; j++){nums[j] = temp[j - left];}return ret;}
};

3. 计算右侧小于当前元素的个数

315. 计算右侧小于当前元素的个数 - 力扣(LeetCode)

        不难发现,本道题目和上一道交易逆序对的总数的处理方法是非常相似的,区别在于,本题统计的是数组每个元素的右侧小于该元素的数量。则如果我们直接使用归并排序来处理,是会出现问题的,因为计算右侧小于当前元素的个数需要根据数组元素的初始下标来记录出现个数,而排序后,数组nums的顺序发生变化,我们就不能直接得到初始下标了。

        我们可以这样解决:维护一个记录nums数组初始下标的数组index,当我们对nums进行排序时,同步对index数组做相应的处理,这样一来,即便我们对nums进行排序,还是能通过index数组来找到数组元素的初始下标。

        还有一点值得一提的是,本题我们的排序应该选择降序排列而非升序排列,这是因为上一题我们求逆序对的总数实际上是通过计算左侧大于当前元素的个数来得到的,而本题要求的是右侧小于当前元素的个数,所以应该让数组降序排列。可以像上一道题一样,画图辅助理解:

        目前为止,本题所有的算法原理讲解完毕,大家可以先试着自己编写一下代码,锻炼一下自己的代码能力,这样才能够最好的提升。

class Solution {
public:vector<int> temp1, temp2;vector<int> index, ret;vector<int> countSmaller(vector<int>& nums) {int n = nums.size();temp1.resize(n);temp2.resize(n);index.resize(n);ret.resize(n, 0);for (int j = 0; j < n; j++)index[j] = j;mergesort(nums, 0, n - 1);return ret;}void mergesort(vector<int>& nums, int left, int right) {if (left >= right) return;int mid = (left + right) / 2;mergesort(nums, left, mid);mergesort(nums, mid + 1, right);int cur1 = left, cur2 = mid + 1, i = 0;while (cur1 <= mid && cur2 <= right) {if (nums[cur1] <= nums[cur2]) {temp1[i] = nums[cur1];temp2[i++] = index[cur1++];} else {ret[index[cur1]] += (right - cur2 + 1); // Update ret[index[cur1]]temp1[i] = nums[cur2];temp2[i++] = index[cur2++];}}while (cur1 <= mid) {temp1[i] = nums[cur1];temp2[i++] = index[cur1++];}while (cur2 <= right) {temp1[i] = nums[cur2];temp2[i++] = index[cur2++];}for (int j = left; j <= right; j++) {nums[j] = temp1[j - left];index[j] = temp2[j - left];}}
};

4. 翻转对

493. 翻转对 - 力扣(LeetCode)

        通过题目描述,大家都能发现本题和第二题也是挺相似的,不过本题要求的是满足i<j且nums[i]>2*nums[j]的(i,j)个数,这个比较条件并不像第二题一样和归并排序的排序过程完美重合,但是通过和第二题相似的思路,我们利用归并排序处理过的两个数组来求翻转对还是可以大大优化效率,所以求翻转对的操作应该是要放在左排序、右排序之后,在合并两个数组之前的。

        另外,int类型是有大小范围限制的,本题给的数据比较大,会出现溢出的情况,所以我们的判断不直接使用nums[i]>2*nums[j],而是使用nums[i]/2.0>nums[j]。

        

class Solution {
public:vector<int> temp;int reversePairs(vector<int>& nums) {temp.resize(nums.size());return mergesort(nums, 0, nums.size() - 1);}int mergesort(vector<int> &nums, int left, int right){if(left >= right) return 0;int ret = 0;int mid = (left + right) >> 1;ret += mergesort(nums, left, mid);ret += mergesort(nums, mid + 1, right);int cur1 = left, cur2 = mid + 1, i = 0;while(cur1 <= mid && cur2 <= right){if(nums[cur1] / 2.0 <= nums[cur2]) cur2++;else{ret += right - cur2 + 1;cur1++;}}cur1 = left, cur2 = mid + 1;while(cur1 <= mid && cur2 <= right){temp[i++] = nums[cur1] <= nums[cur2] ? nums[cur2++] : nums[cur1++]; }while(cur1 <= mid) temp[i++] = nums[cur1++];while(cur2 <= right) temp[i++] = nums[cur2++];for(int j = left; j <= right; j++)nums[j] = temp[j - left];return ret;}
};

总结

        本篇文章从归并排序开始,带着大家使用分治算法解决了几道算法题,通过这几道题目的练习,我们学习到了包括但不限于:

1. 分治算法的思想:将问题分解为小的子问题,递归解决子问题,然后将结果合并来解决原始问题。

2. 归并排序的实现:数组的划分、递归排序和合并操作。

3. 归并排序算法的稳定性,即排序过程中相等元素的相对位置不会发生变化。

4. 通过分治和排序对暴力枚举算法进行优化。

        大家可以收藏本文,以后再碰到分治算法题可以再回过头看看这篇文章,相信会有不一样的理解。

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

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

相关文章

Android View的绘制流程

1.不管是View的添加&#xff0c;还是调用View的刷新方法invalidate()或者requestLayout()&#xff0c;绘制都是从ViewRootImpl的scheduleTraversals()方法开始 void scheduleTraversals() {if (!mTraversalScheduled) {mTraversalScheduled true;mTraversalBarrier mHandler…

Linux中nohup(no hang up)不挂起,用于在系统后台不挂断地运行命令,即使退出终端也不会影响程序的运行。

nohup的英文全称是 no hang up&#xff0c;即“不挂起”。这个命令在Linux或Unix系统中非常有用&#xff0c;主要用于在系统后台不挂断地运行命令&#xff0c;即使退出终端也不会影响程序的运行。默认情况下&#xff08;非重定向时&#xff09;&#xff0c;nohup会将输出写入一…

C++之类与对象(1)

目录 前言 1.类的定义 1.1类定义的格式 1.2访问限定符 1.3类域 1.3.1类定义一个作用域 1.3.2类成员在类的作用域中 1.3.3在类体外定义成员 2.实例化 2.1实例化概念 2.2对象大小 3.this指针 4.选择题补充练习 结束语 前言 Hello&#xff0c;友友们&#xff0c;好久…

Linux安装mysql(超详细版)

步骤1&#xff1a;新建一个文件夹&#xff0c;专放从网络下载的文件 [rootiZ2zeh6vyxsq620zifz8jaZ home]#mkdir soft #在根目录下创建也可以 步骤2&#xff1a;切换目录&#xff0c;进入soft文件中 [rootiZ2zeh6vyxsq620zifz8jaZ /]# cd home/ #若第一步文件建在根目…

牛客周赛 Round 51

目录 A.小红的同余 B.小红的三倍数 C.小红充电 D.小红的gcd E.小红走矩阵 F.小红的数组 这次周赛题目比较简单&#xff0c;算法题也基本上是板子题&#xff0c;出得很好(&#xff5e;&#xffe3;▽&#xffe3;)&#xff5e; A.小红的同余 思路&#xff1a;签到题&am…

Android Studio 不再支持windows 7

Android Studio 一打开就报错&#xff1a; 无法找到入口 无法定位程序输入点 CreateAppContainerProfle 于动态链接库USERENV.dII 上。 截图如下&#xff1a; 经调查&#xff0c;是因为系统版本不兼容。 我目前的电脑环境&#xff1a;windows 7,但是现在的Android Studio要…

24年Hvv准备,6大方向,33篇技战法

进去不少小伙伴后台留言说需要技战法&#xff0c;因此小编对市面上的技战法进行了收集和总结&#xff0c;并对收集来的技战法进行了分类&#xff0c;总共分了6大类&#xff0c;共计33篇&#xff1a; 有需要的小伙伴关注我&#xff0c;点击在看&#xff0c;并私信回复“技战法”…

基于Java的原创歌曲分享平台

你好呀&#xff0c;我是计算机学姐码农小野&#xff01;如果有相关需求&#xff0c;可以私信联系我。 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBootMyEclipse 工具&#xff1a;MyEclipse、B/S架构 系统展示 首页 用户注册界面 音乐分享…

【python】OpenCV—Coordinates Sorted Clockwise

文章目录 1、需求介绍2、算法实现3、完整代码 1、需求介绍 调用 opencv 库&#xff0c;绘制轮廓的矩形边框&#xff0c;坐标顺序为右下→左下→左上→右上&#xff0c;我们实现一下转化为熟悉的 左上→右上→右下→左下 形式 按照这样的顺序组织边界框坐标是执行透视转换或匹…

21天学通C++:第十三、十四章节

第十三章&#xff1a;类型转换运算符 类型转换是一种机制&#xff0c;让程序员能够暂时或永久性改变编译器对对象的解释。注意&#xff0c;这并不意味着程序员改变了对象本身&#xff0c;而只是改变了对对象的解释。可改变对象解释方式的运算符称为类型转换运算符。 为何需要…

数据库端口LookUp功能:从数据库中获取并添加数据到XML

本文将为大家介绍如何使用知行之桥EDI系统数据库端口的Lookup功能&#xff0c;从数据库中获取数据&#xff0c;并添加进输入的XML中。 使用场景&#xff1a;期待以输入xml中的值为判断条件从数据库中获取数据&#xff0c;并添加进输入xml中。 例如&#xff1a;接收到包含采购…

简述乐观锁和悲观锁——Java

悲观锁和乐观锁 悲观就是任何事都认为会往坏处发生&#xff0c;乐观就是认为任何事都会往好处发生。 打个比方&#xff0c;假如一个公司里只有一台打印机&#xff0c;如果多个人同时打印文件&#xff0c;可能出现混乱的问题&#xff0c;他的资料打印在了我的资料上&#xff0…

Yolov8网络结构学习

详解YOLOv8网络结构/环境搭建/数据集获取/训练/推理/验证/导出/部署 深入解析YOLOv8&#xff1a;网络结构与推理过程 YOLO? You Know! --YOLOV8详解 一&#xff1a;yolov8总体结构 1.Backbone:它采用了一系列卷积和 反卷积层只来提取特征&#xff0c;同时也使用了残差连接和…

网站开发:使用VScode安装yarn包和运行前端项目

一、首先打开PowerShell-管理员身份运行ISE 输入命令&#xff1a; set-ExecutionPolicy RemoteSigned 选择“全是”&#xff0c;表示允许在本地计算机上运行由本地用户创建的脚本&#xff0c;没有报错就行了 二、接着打开VScode集成终端 输入 npm install -g yarn 再次输入以…

小程序-3(页面导航+页面事件+生命周期+WXS)

目录 1.页面导航 声明式导航 导航到tabBar页面 导航到非tabBar页面 后退导航 编程式导航 后退导航 导航传参 声明式导航传参 编程式导航传参 在onload中接收导航参数 2.页面事件 下拉刷新 停止下拉刷新的效果 ​编辑 上拉触底 配置上拉触底距离 上拉触底的节…

js补环境系列之剖析:原型、原型对象、实例对象三者互相转化(不讲废话、全是干货)

【作者主页】&#xff1a;小鱼神1024 【擅长领域】&#xff1a;JS逆向、小程序逆向、AST还原、验证码突防、Python开发、浏览器插件开发、React前端开发、NestJS后端开发等等 思考下&#xff1a;js补环境中&#xff0c;什么场景会用到原型、原型对象、实例对象&#xff1f; 举…

谈谈大数据采集和常见问题

01 什么是数据采集 数据采集是大数据的基石&#xff0c;不论是现在的互联网公司&#xff0c;物联网公司或者传统的IT公司&#xff0c;每个业务流程环节都会产生大量的数据&#xff0c;同时用户操作的日志也会产生大量的数据&#xff0c;为了将这些结构化和非结构化的数据进行…

docker网络互联

最近学习docker的时候发现多了很多网卡&#xff0c;这些似乎都和docker有关&#xff0c;所以我便往下深入了解了一番&#xff1b; 一、docker网卡 docker 0是安装 docker 的时候生成的虚拟网桥&#xff0c;它在内核层连通了其他物理或者虚拟网卡&#xff0c;这就可以将所…

常用优秀内网穿透工具(实测详细版)

文章目录 1、前言2、安装Nginx3、配置Nginx4、启动Nginx服务4.1、配置登录页面 5、内网穿透5.1、cpolar5.1.1、cpolar软件安装5.1.2、cpolar穿透 5.2、Ngrok5.2.1、Ngrok安装5.2.2、随机域名5.2.3、固定域名5.2.4、前后端服务端口 5.3、NatApp5.4、Frp5.4.1、下载Frp5.4.2、暴露…

【数据结构】--- 栈和队列

前言 前面学习了数据结构的顺序表、单链表、双向循环链表这些结构&#xff1b;现在就来学习栈和队列&#xff0c;这里可以简单的说栈和队列是具有特殊化的线性表 一、栈 1.1、栈的概念和结构 栈是一种遵循先入后出逻辑的线性数据结构。 栈是一种特殊的线性表&#xff0c;它只允…