算法系列--分治排序|归并排序|逆序对的求解

一.基本概念与实现

归并排序(mergeSort)也是基于分治思想的一种排序方式,思路如下:

  1. 分解:根据中间下标mid将数组分解为两部分
  2. 解决:不断执行上述分解过程,当分解到只有一个元素时,停止分解,此时就是有序的
  3. 合并:合并两个有序的子区间,所有子区间合并的结果就是原问题的解

归并排序和快速排序的区别在于排序的时机不同,归并排序是等到分解完毕之后在进行排序,快速排序是先进行分解,再排序;更形象的说,归并排序更像二叉树的后序遍历,遍历完左右子树再打印根节点;快速排序反之
在这里插入图片描述

代码:

class Solution {int[] tmp;public int[] sortArray(int[] nums) {tmp = new int[nums.length];mergeSort(nums, 0, nums.length - 1);return nums;}private void mergeSort(int[] nums, int start, int end) {if(start >= end) return;// 递归出口int mid = (start + end) >> 1;// 分解左右子树mergeSort(nums, start, mid);mergeSort(nums, mid + 1, end);// 合并两个有序序列merge(nums, start, mid, end);}private void merge(int[] nums, int l, int mid, int r) {// 合并两个有序列表int i = 0, i1 = l, i2 = mid + 1;while(i1 <= mid && i2 <= r)tmp[i++] = nums[i1] < nums[i2] ? nums[i1++] : nums[i2++];while(i1 <= mid) tmp[i++] = nums[i1++];while(i2 <= r) tmp[i++] = nums[i2++];// 重新赋值for(int j = l; j <= r; j++) nums[j] = tmp[j - l];}
}
  • 将tmp设置为全局减少每次合并两个有序列表都要new所消耗的时间

2.利用归并排序求解逆序对

逆序对
链接:https://leetcode.cn/problems/shu-zu-zhong-de-ni-xu-dui-lcof/description/
在这里插入图片描述
分析

  • 最容易想到的思路就是暴力解法,每遍历到一个数字就统计后面有多少个小于当前数字的数字,只要小于就满足逆序对的条件,时间复杂度为O(N^2),显而易见这种做法的时间复杂度过高,无法通过所有样例

  • 可以这么想,每遍历到一个数字,我就想"如果我知道我前面有多少个比我大的数字该多好",就不需要再去一个一个遍历了,或者是每遍历到一个数字,“如果我知道后面有多少个比我小的数字该多好”

  • 但是实际上不容易解决,不容易统计,比如9,7,8这样的一个序列,你无法通过记录最值的方式统计有多少个大的数字,这是行不通的,好像唯一的解法就是从开头一直遍历到当前数字?可这就是暴力解法啊,如果能对前面的区间进行排序,得到大于当前数字的所有数字中的最小值,就能根据下标快速得到有多少个比我大的数字.那难道每遍历到一个数字就对前面的区间排一下序再找最小值吗?时间复杂度好像更高了,且不稳定

  • 但是这个想法是好的,在遍历的过程中如果前面的元素是有序的,那么就能降低时间复杂度,关于逆序对的求解,排序是一个很好的思路

  • 排序一般都是从小到大排序,无论哪种排序方式,对于一个乱序的数组,如果想让他变成有序的,就必须要进行元素的移动,就会将较小的数字放到较大的数字之前,可见排序就是消除逆序对的过程,以下是几种基于排序的求解逆序对的方法

01.冒泡排序

  • 对于冒泡排序,每次元素交换就可以看做一次消除逆序对;使用全局变量ret记录元素交换的次数
  • 冒泡排序的本质是"每次移动,都通过元素比较和元素交换让当前区间的最大值移动到区间末尾"

代码:

class Solution {// 冒泡排序法int ret;public int reversePairs(int[] nums) {bubbleSort(nums);return ret;}private void bubbleSort(int[] nums) {int n = nums.length;for (int i = 0; i < n; i++) {boolean flg = false;for (int j = 0; j < n - 1 - i; j++) {if (nums[j] > nums[j + 1]) {swap(nums, j, j + 1);flg = true;}}if (!flg)return;}}private void swap(int[] nums, int i, int j) {int tmp = nums[i];nums[i] = nums[j];nums[j] = tmp;++ret;}
}
  • 时间复杂度:O(N^2)

02.插入排序

  • 对于插入排序,每次元素的移动都可以看做一次消除逆序对,使用一个全局变量ret记录元素移动的次数即可,时间比冒泡排序略快
  • 插入排序就像是往你的已有的有序的扑克牌中插入一个新牌,每次都是从后往前进行比较,进行插入

代码:

class Solution {// 插入排序法int ret;public int reversePairs(int[] nums) {for(int i = 1; i < nums.length; i++) {int tmp = nums[i], j = i - 1;while(j >= 0) {if(nums[j] > tmp) {nums[j + 1] = nums[j];++ret;}else break;--j;}nums[j + 1] = tmp;}return ret;}
}
  • 时间复杂度:O(N^2)

03.归并排序(最快的解法)

代码:

class Solution {int[] tmp;// 用于合并两个有序数组的临时数组int ret;// 记录递归过程中的逆序对的个数public int reversePairs(int[] nums) {tmp = new int[nums.length];mergeSort(nums, 0, nums.length - 1);// 归并排序return ret;}private void mergeSort(int[] nums, int start, int end) {if(start >= end) return;int mid = (start + end) >> 1;mergeSort(nums, start, mid);mergeSort(nums, mid + 1, end);merge(nums, start, mid, end);}private void merge(int[] nums, int l, int mid, int r) {// 在合并的过程中统计逆序对的个数// 关键步骤  合并两个有序数组// 在合并的过程中统计逆序对的个数!!!// 此处采用 的逻辑是:固定cur2,找cur1中比当前cur2大的数// 在排序的过程中应该使用升序排序int i = 0, i1 = l, i2 = mid + 1;while(i1 <= mid && i2 <= r) {if(nums[i1] > nums[i2]) {// 如果大  则i1后面的数字都比当前数字大  都能构成逆序对ret += mid - i1 + 1;tmp[i++] = nums[i2++];}else {tmp[i++] = nums[i1++];}}while(i1 <= mid) tmp[i++] = nums[i1++];while(i2 <= r) tmp[i++] = nums[i2++];for(int j = l; j <= r; j++) nums[j] = tmp[j - l];}
}
  • 时间复杂度:O(N*logN)

图解:
在这里插入图片描述
在这里插入图片描述


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

链接:https://leetcode.cn/problems/count-of-smaller-numbers-after-self/description/
分析

  • 是上一题逆序对的拓展版本
  • 利用上面快速求解逆序对的思想,能够快速得到小于当前数字的数字的个数,需要注意的是,在归并排序的过程中原数组的元素会发生移动,但是只有归并完整个数组才能得到最终的结果,这个过程中我们要将数组元素和数组下标进行绑定,可以考虑使用index数组
  • 当元素发生移动时,下标也跟着移动;

代码:

class Solution {int[] tmpNum;int[] tmpIndex;int[] index;int[] cnt;public List<Integer> countSmaller(int[] nums) {int n = nums.length;tmpNum = new int[n];tmpIndex = new int[n];index = new int[n];cnt = new int[n];for(int i = 0; i < n; i++) index[i] = i;// 绑定索引mergeSort(nums, 0, n - 1);List<Integer> ret = new ArrayList<>();for(int x : cnt) ret.add(x);return ret;}private void mergeSort(int[] nums, int start, int end) {if(start >= end) return;int mid = (start + end) >> 1;mergeSort(nums, start, mid);mergeSort(nums, mid + 1, end);merge(nums, start, mid, end);}private void merge(int[] nums, int l, int mid, int r) {// 合并两个有序列表(降序排序)// 可以快速得到有多少个数字小于当前数字int i = 0, i1 = l, i2 = mid + 1;while(i1 <= mid && i2 <= r) {if(nums[i1] > nums[i2]) {cnt[index[i1]] += r - i2 + 1;tmpIndex[i] = index[i1];tmpNum[i++] = nums[i1++];}else  {tmpIndex[i] = index[i2];tmpNum[i++] = nums[i2++];}}// 处理未解决的元素while(i1 <= mid) {tmpIndex[i] = index[i1];tmpNum[i++] = nums[i1++];}while(i2 <= r) {tmpIndex[i] = index[i2];tmpNum[i++] = nums[i2++];}// 重新赋值  原数组和下标数组都要更改for(int j = l; j <= r; j++) {nums[j] = tmpNum[j - l];index[j] = tmpIndex[j - l];}}
}

4.反转对的个数

链接:

代码:

class Solution {int[] tmp;int ret;public int reversePairs(int[] nums) {int n = nums.length;tmp = new int[n];mergeSort(nums, 0, n - 1);return ret;}private void mergeSort(int[] nums, int start, int end) {if(start >= end) return;int mid = (start + end) >> 1;mergeSort(nums, start, mid);mergeSort(nums, mid + 1, end);merge(nums, start, mid, end);}private void merge(int[] nums, int l, int mid, int r) {// 先统计当前有多少个翻转对int i = 0, i1 = l, i2 = mid + 1;while(i1 <= mid) {while(i2 <= r && nums[i2] >= nums[i1] / 2.0) i2++;// 注意此处的除法需要存在小数位if(i2 > r) break;ret += r - i2 + 1;i1++;}i1 = l; i2 = mid + 1;while(i1 <= mid && i2 <= r) {if(nums[i1] > nums[i2]) tmp[i++] = nums[i1++];else tmp[i++] = nums[i2++];}while(i1 <= mid) tmp[i++] = nums[i1++];while(i2 <= r) tmp[i++] = nums[i2++];for(int j = l; j <= r; j++) nums[j] = tmp[j - l];}
}

注意:

  1. 使用除法进行判断是为了防止越界
  2. /2和/2.0的结果是不同的
  3. 本题在合并两个有序列表之前需要先统计翻转对的个数,具体做法就是暴力遍历两个数组,不能在合并的时候统计翻转对的个数

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

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

相关文章

【TB作品】51单片机 Proteus仿真 00002仿真-智能台灯色调倒计时光强

实验报告&#xff1a;基于51单片机的智能台灯控制系统 背景 本实验旨在设计一个基于51单片机的智能台灯控制系统&#xff0c;该系统可以通过按键进行手动控制&#xff0c;并能根据环境光强度自动调节台灯亮度。此外&#xff0c;系统还具备倒计时关灯功能。 器件连接 51单片…

Xilinx FPGA:vivado关于真双端口的串口传输数据的实验

一、实验内容 用一个真双端RAM&#xff0c;端口A和端口B同时向RAM里写入数据0-99&#xff0c;A端口读出单数并存入单端口RAM1中&#xff0c;B端口读出双数并存入但端口RAM2中&#xff0c;当检测到按键1到来时将RAM1中的单数读出显示到PC端&#xff0c;当检测到按键2到来时&…

强化学习的数学原理:时序差分算法

概述 之前第五次课时学习的 蒙特卡洛 的方法是全课程当中第一次介绍的第一种 model-free 的方法&#xff0c;而本次课的 Temporal-Difference Learning 简称 TD learning &#xff08;时序差分算法&#xff09;就是第二种 model-free 的方法。而对于 蒙特卡洛方法其是一种 non…

QWidget窗口抗锯齿圆角的一个实现方案(支持子控件)2

QWidget窗口抗锯齿圆角的一个实现方案&#xff08;支持子控件&#xff09;2 本方案使用了QGraphicsEffect&#xff0c;由于QGraphicsEffect对一些控件会有渲染问题&#xff0c;比如列表、表格等&#xff0c;所以暂时仅作为研究&#xff0c;优先其他方案 在之前的文章中&#…

论文辅助笔记:ST-LLM

1 时间嵌入 2 PFA&#xff08;Partial Frozen Architecture&#xff09; 3 ST_LLM 3.1 初始化 3.2 forward

Idea新增Module报错:sdk ‘1.8‘ type ‘JavaSDK‘ is not registered in ProjectJdkTable

文章目录 一&#xff0c;创建Module报错二&#xff0c;原因分析三&#xff0c;解决方案1&#xff0c;点击上图的加号&#xff0c;把JDK8添加进来即可2&#xff0c;点击左侧[Project]&#xff0c;直接设置SDK为JDK8 四&#xff0c;配置检查与验证 一&#xff0c;创建Module报错 …

【Linux】:进程创建与终止

朋友们、伙计们&#xff0c;我们又见面了&#xff0c;本期来给大家解读一下有关Linux程序地址空间的相关知识点&#xff0c;如果看完之后对你有一定的启发&#xff0c;那么请留下你的三连&#xff0c;祝大家心想事成&#xff01; C 语 言 专 栏&#xff1a;C语言&#xff1a;从…

dell Vostro 3690安装win11 23h2 方法

下载rufus-4.5.exe刻U盘去除限制 https://www.dell.com/support/home/zh-cn/product-support/product/vostro-3690-desktop/drivers dell官网下载驱动解压到U盘 https://dl.dell.com/FOLDER09572293M/2/Intel-Rapid-Storage-Technology-Driver_88DM9_WIN64_18.7.6.1010_A00_01…

【鸿蒙学习笔记】创建自定义组件

官方文档&#xff1a;创建自定义组件 目录标题 [Q&A] 如何自定义组件&#xff1f;&#xff11;・struct 自定义组件名 {...}&#xff12;・build()函数&#xff1a;&#xff13;・&#xff20;Component&#xff14;・Entry&#xff15;・Reusable 自定义组件的参数 buil…

GD32 MCU ADC采样率如何计算?

大家在使用ADC采样的时候是否计算过ADC的采样率&#xff0c;这个问题非常关键&#xff01; 以下为GD32F303系列MCU中有关ADC的参数&#xff0c;其中ADC时钟最大值为40MHz&#xff0c;12位分辨率下最大采样率为2.86MSPS.如果ADC时钟超频的话&#xff0c;可能会造成ADC采样异常&…

SAP_MM模块-采购信息记录变更文档的三种查询方式

最近有用户在问采购信息记录变更的信息怎么去查找&#xff0c;想要看看是谁更改了价格&#xff0c;于是就给她查了一下&#xff0c;顺便做个记录&#xff0c;SAP中的业务数据或者主数据的变更信息查询方法&#xff0c;都是比较类似的&#xff0c;学会了这三个方法&#xff0c;其…

商家店铺电商小程序模板源码

橙色通用的商家入驻&#xff0c;商户商家&#xff0c;商家店铺&#xff0c;购物商城&#xff0c;商家购物平台app小程序网页模板。包含&#xff1a;商家主页、优先商家、商品详情、购物车、结算订单、个人中心、优惠券、会员卡、地址管理等功能页面。 商家店铺电商小程序模板源…

SSM高校教师教学质量评估系统-计算机毕业设计源码03344

摘要 在高等教育中&#xff0c;教学质量是培养优秀人才的关键。为了提高教学质量&#xff0c;高校需要建立一套科学、有效的教师教学质量评估系统。本研究采用 SSM技术框架&#xff0c;旨在开发一款高校教师教学质量评估系统。 SSM框架作为一种成熟的Java开发框架&#xff0c;具…

Centos新手问题——yum无法下载软件

起因&#xff1a;最近在学习centos7&#xff0c;在VM上成功安装后&#xff0c;用Secure进行远程登陆。然后准备下载一个C编译器&#xff0c;看网络上的教程&#xff0c;都是用yum来下载&#xff0c;于是我也输入了命令&#xff1a; yum -y install gcc* 本以为会自动下载&…

DevEco Studio无法识别本地模拟器设备的解决方法

目录 场景 解决办法 方式1 方式2 场景 有很多小伙伴遇到过安装了手机模拟器, 但是开发工具设备栏不识别手机设备的问题, 如下图,明明模拟器都安装了,并启动, 但为什么设备栏不显示呢? 解决后的截图,应该是这样(其实跟 android 类似 )

阶段三:项目开发---搭建项目前后端系统基础架构:任务11:搭建项目后台系统基础架构

任务描述 1、了解搭建民航后端框架 2、使用IDEA创建基于SpringBoot、MyBatis、MySQL、Redis的Java项目 3、以原项目为参照搭建项目所涉及到的各个业务和底层服务 4、以原项目为例&#xff0c;具体介绍各个目录情况并参照创建相关文件夹 任务指导 1、讲框架的选择和原理 …

数据库系统原理练习 | 作业1-第1章绪论(附答案)

整理自博主本科《数据库系统原理》专业课完成的课后作业&#xff0c;以便各位学习数据库系统概论的小伙伴们参考、学习。 *文中若存在书写不合理的地方&#xff0c;欢迎各位斧正。 专业课本&#xff1a; 目录 一、选择题 二&#xff1a;简答题 三&#xff1a;综合题 一、选择…

『古籍自有答案』古风H5案例赏析

「古籍自有答案」&#xff0c;一部由新京报与字节跳动公益联合打造的古风H5&#xff0c;以诗意盎然的开篇引领用户穿梭于千年文脉。 part1. 创意定位 "人生有惑问先贤&#xff0c;先贤答案存古籍"&#xff0c;在这里&#xff0c;每一个灵魂的探问&#xff0c;都能在…

【电商系统开发实用接口指南】包含国内国外多电商平台商品数据对接(附文档)

关于电商数据接口 开发电商系统的朋友对于电商平台API肯定不陌生&#xff0c;API接口即应用程序编程接口&#xff0c;电商平台开放部分API接口&#xff0c;供商家和服务商调用&#xff0c;以满足电商业务管理需求。随着电商市场需求的日益增长以及技术手段的不断成熟&#xf…

推荐3款【王炸级别】的效率软件,免费无广告,你一定要收藏

Temp Cleaner Temp Cleaner 是一款专为 Windows 操作系统设计的临时文件清理工具。它的主要功能是安全且快速地清理磁盘上的临时文件和系统缓存&#xff0c;从而释放磁盘空间。该软件体积小巧&#xff08;仅有826KB&#xff09;&#xff0c;并且是无广告的绿色软件&#xff0c;…