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

一.基本概念与实现

归并排序(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.mzph.cn/web/41303.shtml

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

相关文章

第一节 网络安全概述

一.网络空间安全 网络空间&#xff1a;一个由信息基础设施组成相互依赖的网络。 ---- 海陆空天&#xff08;大海、陆 地、天空、航天&#xff09; 通信保密阶段 ---- 计算机安全 ----- 信息系统安全 ----- 网络空间安全 计算机安全&#xff1a;开始秉持着“严于律己&#x…

C语言 指针和数组—指针数组及其在字符串处理中的应用

目录 问题的提出 问题的解决 回头看——指针、数组及其他类型的混合 指针数组与指向数组的指针 字符串的排序 问题的提出 问题的解决 回头看——指针、数组及其他类型的混合  基本数据类型  int 、 long 、 char 、 short 、 float 、 double……  数组是一种从…

os.makedirs

官方说明文档&#x1f517;&#xff1a;Link 解释下面的代码&#xff1a; os.makedirs(os.path.join(args.output_dir,sample_images), exist_okTrue)os.makedirs()&#xff1a;这是一个用于递归创建目录的Python函数。如果中间级目录&#xff08;目录链中的所有目录&#xff…

The IsA relationship and HasA relationship

Why you should worry about that? or not. Is-A (Inheritance) Represents an “is-a-kind-of” hierarchy between classes. A subclass (child class) inherits attributes and methods from its superclass (parent class). Subclasses can specialize or override inh…

设计模式之模版方法

模版方法介绍 模版方法&#xff08;Template Method&#xff09;模式是一种行为型设计模式&#xff0c;它定义了一个操作&#xff08;模板方法&#xff09;的基本组合与控制流程&#xff0c;将一些步骤&#xff08;抽象方法&#xff09;推迟到子类中&#xff0c;使得子类可以在…

旅游 | 西岳华山

得到了再失去&#xff0c; 总比从来没有得到更伤人。 ——胡赛尼《追风筝的人》 目录 旅游 | 西岳华山00 | 旅游导图01 | 旅游路线02 | 必带行李03 | 旅游费用3.1 门票3.2 索道价格3.2.1 北峰索道&#xff08;单程&#xff09;3.2.1 西峰索道&#xff08;单程&#xff09; 3.3 …

掌握 IPython 历史的艺术:%dhist 命令的深度指南

掌握 IPython 历史的艺术&#xff1a;%dhist 命令的深度指南 在 IPython 的交互式探索中&#xff0c;历史命令是我们最宝贵的资源之一。%dhist 命令是 IPython 提供的一个强大工具&#xff0c;它允许用户浏览、搜索和重新执行历史中的命令。本文将深入探讨 %dhist 命令的使用方…

【UE5.1】Chaos物理系统基础——03 炸开几何体集

目录 步骤 一、通过径向向量将几何体集炸开 二、优化炸开效果——让破裂的碎块自然下落 三、优化炸开效果——让碎块旋转起来 四、优化炸开效果——让碎块旋转的越来越慢 步骤 一、通过径向向量将几何体集炸开 1. 打开上一篇中&#xff08;【UE5.1】Chaos物理系统基础—…

Spring IOC基于XML和注解管理Bean

IoC 是 Inversion of Control 的简写&#xff0c;译为“ 控制反转 ”&#xff0c;它不是一门技术&#xff0c;而是一种设计思想&#xff0c;是一个重要的面向对象编程法则&#xff0c;能够指导我们如何设计出 松耦合、更优良的程序。 Spring 通过 IoC 容器来管理所有 Java 对象…

如何从 Windows 11/10/8.1/8/7 恢复已删除的视频

意外删除了视频或格式化了 SD 卡/硬盘&#xff1f;没有备份已删除的视频&#xff1f;别担心&#xff0c;我们有解决方案来恢复 Windows 11、10 中已删除的视频并处理这种糟糕的情况。 但在了解如何恢复已删除的视频和视频恢复应用程序之前&#xff0c;请知道 Windows 会为您提…

ARMv8寄存器详解

文章目录 一、ARMv8寄存器介绍二、通用寄存器三、 PSTAE寄存器四、特殊寄存器五、系统寄存器 一、ARMv8寄存器介绍 本文我来给大家介绍一下ARMv8的寄存器部分&#xff0c;ARMv8中有34个寄存器&#xff0c;包括31个通用寄存器、一个栈指针寄存器SP(X31),一个程序计数器寄存器PC…

Apache Drill 2万字面试题及参考答案

目录 什么是Apache Drill? Apache Drill的主要特点是什么? Apache Drill如何实现对复杂数据的查询? 描述Apache Drill的数据存储模型。 为什么Apache Drill被称为自服务的SQL查询引擎? Apache Drill支持哪些类型的数据源? 解释Apache Drill中的“schema discovery”…

Transformer前置知识:Seq2Seq模型

Seq2Seq model Seq2Seq&#xff08;Sequence to Sequence&#xff09;模型是一类用于将一个序列转换为另一个序列的深度学习模型&#xff0c;广泛应用于自然语言处理&#xff08;NLP&#xff09;任务&#xff0c;如机器翻译、文本摘要、对话生成等。Seq2Seq模型由编码器&#…

《框架封装 · 统一异常处理和返回值包装》

&#x1f4e2; 大家好&#xff0c;我是 【战神刘玉栋】&#xff0c;有10多年的研发经验&#xff0c;致力于前后端技术栈的知识沉淀和传播。 &#x1f497; &#x1f33b; CSDN入驻不久&#xff0c;希望大家多多支持&#xff0c;后续会继续提升文章质量&#xff0c;绝不滥竽充数…

贪心算法-以高校科研管理系统为例

1.贪心算法介绍 1.算法思路 贪心算法的基本思路是从问题的某一个初始解出发一步一步地进行&#xff0c;根据某个优化测度&#xff0c;每一 步都要确保能获得局部最优解。每一步只考虑一 个数据&#xff0c;其选取应该满足局部优化的条件。若下 一个数据和部分最优解连在一起…

JavaEE初阶-网络原理1

文章目录 前言一、UDP报头二、UDP校验和2.1 CRC2.2 md5 前言 学习一个网络协议&#xff0c;最主要就是学习的报文格式&#xff0c;对于UDP来说&#xff0c;应用层数据到达UDP之后&#xff0c;会给应用层数据报前面加上UDP报头。 UDP数据报UDP包头载荷 一、UDP报头 如上图UDP的…

Kubernetes(K8s) kubectl 常用命令

文章目录 一、常用命令1.1 kubectl describe 命令 二、kubectl 命令中的简写三、Helm3.1 常用命令&#xff1a;3.2 遇到的问题3.2.1 cannot re-use a name that is still in use 四、Containerd 一、常用命令 检查 k8s 各节点状态&#xff0c;确保k8s集群各节点状态正常&#x…

概率基础——矩阵正态分布matrix normal distribution

矩阵正态分布-matrix normal distribution 定义性质应用 最近碰到了这个概念&#xff0c;记录一下 矩阵正态分布是一种推广的正态分布&#xff0c;它应用于矩阵形式的数据。矩阵正态分布在多维数据分析、贝叶斯统计和机器学习中有广泛的应用。其定义和性质如下&#xff1a; 定…

Emacs之解决:java-mode占用C-c C-c问题(一百四十六)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

【django项目使用easycython编译】Cannot convert Unicode string to ‘str‘ implicitly.

django项目编译遇到的问题 报错条件 需要编译的python源码里面的函数写了type hint&#xff0c;尤其是return的type hint&#xff0c; 当type hint是str时&#xff0c;但是变量确实f-string格式化后得到的&#xff0c;编译时会报错 报错原因 easycython会检查变量类型&…