【二十七】【算法分析与设计】归并(1),912. 排序数组,归并排序,递归函数的时间复杂度计算,LCR 170. 交易逆序对的总数

912. 排序数组

给你一个整数数组 nums,请你将该数组升序排列。

示例 1:

输入:nums = [5,2,3,1] 输出:[1,2,3,5]

示例 2:

输入:nums = [5,1,1,2,0,0] 输出:[0,0,1,1,2,5]

提示:

  • 1 <= nums.length <= 5 * 10(4)

  • -5 * 10(4) <= nums[i] <= 5 * 10(4)

递归函数,定义递归函数mergeSortnums数组中[left,right]区间元素进行升序排序。

递归内部逻辑,将[left,mid][mid+1,right]两个区间分别进行排序,排序完的两个独立的升序的区间,利用双指针进行合并。

递归的出口是left>=right,表示区间已经没有元素或者只有一个元素的情况,此时不需要进行排序操作。

内部递归逻辑维护意义的代码,实际上是利用双指针将两个有序的区间[left,mid][mid+1,right]进行合并的过程。

双指针遍历两个部分,将小的尾插到tmp临时数组中,直到所有元素都存储在tmp数组中。

定义将升序区间[left,mid][mid+1,right]两个区间分割出两个待处理的区间,[left,cur1-1][cur1,mid][mid+1,cur2-1][cur2,right]

定义end1=mid,end2=right,得到最终的区间划分,[left,cur1-1][cur1,end1][mid+1,cur2-1][cur2,end2]

[left,cur1-1][mid+1,cur2-1]全都是已经处理完的区间,[cur1,end1][cur2,end2]是待处理的区间。

定义tmp数组,和index[0,index-1][index,right-left]区间划分。

总区间长度是right-left+1,[0,index-1]表示处理完毕的区间,[index,right-left]表示待处理的区间。

while (cur1 <= end1 && cur2 <= end2) tmp[index++] = nums[cur1] <= nums[cur2] ? nums[cur1++] : nums[cur2++];

不断地维护numstmp区间的定义。当有一个区间待处理区间没有元素时,循环退出。此时需要把另一个还没有处理完的区间剩余元素添加到tmp数组中。

while (cur1 <= end1) tmp[index++] = nums[cur1++]; while (cur2 <= end2) tmp[index++] = nums[cur2++];

维护区间定义。

for (int i = left; i <= right; i++) nums[i] = tmp[i - left];

最后将tmp临时数组,排好序的依次赋值给nums数组中,完成合并排序。

 
class Solution {
public:vector<int> tmp;vector<int> sortArray(vector<int>& nums) {tmp.resize(nums.size());mergeSort(nums, 0, nums.size() - 1);return nums;}void mergeSort(vector<int>& nums, int left, int right) {// 定义mergeSort递归函数,将nums数组中[left,right]区间进行排序// 内部逻辑,将[left,mid][mid+1,right]左右区间分别进行排序,排序完的利用双指针合并排序// 因此递归出口是left>=right表示区间只有一个元素或者没有元素,不需要再排序if (left >= right)return;int mid = left + (right - left) / 2;mergeSort(nums, left, mid);mergeSort(nums, mid + 1, right);int cur1 = left, end1 = mid;int cur2 = mid + 1, end2 = right;int index = 0;while (cur1 <= end1 && cur2 <= end2)tmp[index++] =nums[cur1] <= nums[cur2] ? nums[cur1++] : nums[cur2++];while (cur1 <= end1)tmp[index++] = nums[cur1++];while (cur2 <= end2)tmp[index++] = nums[cur2++];for (int i = left; i <= right; i++)nums[i] = tmp[i - left];}
};

vector<int> tmp; 声明了一个成员变量 tmp,它是一个整型向量,用于临时存储排序过程中的元素。

sortArray 函数

tmp.resize(nums.size()); 调整 tmp 的大小,使其与输入数组 nums 的大小相同,这是为了确保有足够的空间来存储排序过程中的数据。

mergeSort(nums, 0, nums.size() - 1); 调用 mergeSort 函数,对整个数组进行排序。这里传入 nums、起始索引 0 和结束索引 nums.size() - 1 作为参数。

return nums; 返回排序后的数组。

mergeSort 函数

void mergeSort(vector<int>& nums, int left, int right) { 定义了一个函数 mergeSort,它接收三个参数:一个整型向量的引用 nums 和两个整数 leftright,分别代表要排序的数组部分的起始和结束索引。这个函数用递归方式实现归并排序。

if (left >= right) 检查递归的基本情况,如果当前区间只有一个元素或无元素(即 left 大于等于 right),就不需要排序,直接返回。

int mid = left + (right - left) / 2; 计算中点 mid,这样可以把数组分成两部分。

mergeSort(nums, left, mid); 递归地对左半部分进行排序。

mergeSort(nums, mid + 1, right); 递归地对右半部分进行排序。

合并两个排序后的部分

首先,通过双指针方法遍历两个部分(左半部分由 cur1end1 控制,右半部分由 cur2end2 控制),比较指针所指的元素,将较小的元素移动到临时数组 tmp 中。

while 循环用来合并两个部分,直到一个部分的元素全部移动到 tmp

当一部分的元素全部移动到tmp中,剩下一部分的元素全部加入tmp即可。

for 循环将临时数组 tmp 中已排序的元素复制回原数组 nums 的相应位置,完成合并操作。

递归函数的时间复杂度计算

具体到时间复杂度的计算,我们可以从分解、解决问题和合并三个步骤来进行分析。分解时间是指将一个待排序序列分解成两序列的时间,这个过程的时间复杂度是O(1),因为它只需要进行一次比较就可以完成。解决问题的时间,即对这两个子序列进行排序的时间,根据归并排序的定义,这一步实际上是递归地对每个子序列进行归并排序,因此这部分的时间复杂度是T(n/2) + T(n/2),其中n是原始数组的长度。合并时间是指将两个有序的子序列合并成一个有序序列的时间,这个操作的时间复杂度是O(n)。

将这三个步骤的时间复杂度相加,我们得到归并排序的总时间复杂度为O(1) + 2T(n/2) + O(n)。

O(1)忽略不计,得到T(n)=2*T(n/2)+O(n)。

f(n) 是关于 n 的一个函数,Θ(n(d)),d 代表复杂度的阶数。根据 a, b, d 不同的取值,我们可以借助 Master Theorem 来求得不同情况下的复杂度:

空间复杂度

归并排序的空间复杂度主要由临时数组 tmp 和递归调用栈所使用的空间组成:

临时数组 tmp: 在整个排序过程中,需要一个大小与原数组 nums 相同的临时数组,所以临时数组的空间复杂度是 O(N)

递归调用栈 归并排序是通过递归实现的,最大的递归深度与数组二分的次数相同,即 O(log N)

因此,归并排序的总空间复杂度是 O(N + log N)。由于在分析空间复杂度时常常忽略低阶项和常数项,可以简化为 O(N)

LCR 170. 交易逆序对的总数

在股票交易中,如果前一天的股价高于后一天的股价,则可以认为存在一个「交易逆序对」。请设计一个程序,输入一段时间内的股票交易记录 record,返回其中存在的「交易逆序对」总数。

示例 1:

输入:record = [9, 7, 5, 4, 6] 输出:8 解释:交易中的逆序对为 (9, 7), (9, 5), (9, 4), (9, 6), (7, 5), (7, 4), (7, 6), (5, 4)。

限制:

0 <= record.length <= 50000

定义递归函数mergeSort,计算nums数组[left,right]区间中的逆序对,并且将区间[left,right]进行升序排序。

内部逻辑,计算[left,mid][mid+1,right]区间中的逆序对,并且将其升序排序,接着计算左右逆序对个数。

递归出口,left>=right,表示只有一个元素或者没有元素时,此时无逆序对。

内部代码实现合并排序以及左右逆序对的计算。

合并升序排序的逻辑,定义nums数组中[left,mid][mid+1,right]两段升序区间,割裂出两段未处理的区间。

得到[left,cur1-1][cur1,mid][mid+1,cur2-1][cur2,right]

定义end1==mid,end2=right

得到[left,cur1-1][cur1,end1][mid-1,cur2-1][cur2,end2]

[left,cur1-1]和[mid+1,cur2-1]区间是已经处理完毕的区间。

定义tmp数组,[0,index-1][index,right-left]

[0,index-1]是处理完毕的区间,[index,right-left]是待处理区间。

依次将小值加入到tmp数组中。

内部代码计算左右逆序对。固定左边一个元素,然后计算右边比这个元素小的有多少个。或者固定右边一个元素,然后计算左边比这个元素大的有多少个。简单来说就是找到所有的左右二元组。

由于两部分都是有序的区间,所以在找二元组的时候可以进行优化。

为了将排序和计算合并在一起,我们先编写排序的代码,然后将计算左右逆序对的时候选取合适的方式进行编写。

 
class Solution {
public:vector<int> tmp;int reversePairs(vector<int>& nums) {tmp.resize(nums.size());return mergeSort(nums, 0, nums.size() - 1);}int mergeSort(vector<int>& nums, int left, int right) {// 定义mergeSort递归函数,返回nums数组中[left,right]的逆序对数,计算完逆序对后排序// 递归出口,left>=right,只有一个元素或者无元素,无逆序对// 内部逻辑,[left,mid][mid+1,right]区间的逆序对+左右逆序对if (left >= right)return 0;int ret = 0;int mid = left + (right - left) / 2;ret += mergeSort(nums, left, mid);ret += mergeSort(nums, mid + 1, right);int cur1 = left, end1 = mid;int cur2 = mid + 1, end2 = right;int index = 0;while (cur1 <= end1 && cur2 <= end2) {if (nums[cur1] <= nums[cur2]) {tmp[index++] = nums[cur1++];} else {ret += end1 - cur1 + 1;tmp[index++] = nums[cur2++];}}while (cur1 <= end1)tmp[index++] = nums[cur1++];while (cur2 <= end2)tmp[index++] = nums[cur2++];for (int i = left; i <= right; i++)nums[i] = tmp[i - left];return ret;}
};

vector<int> tmp; 声明一个整型向量 tmp 用于归并过程中临时存储元素。

reversePairs 函数

tmp.resize(nums.size()); 调整 tmp 的大小使其与 nums 相同,为归并排序过程准备空间。

return mergeSort(nums, 0, nums.size() - 1); 调用 mergeSort 函数并返回其结果。这个函数会对 nums 进行排序,并计算逆序对数量。

mergeSort 函数

int mergeSort(vector<int>& nums, int left, int right) { 定义 mergeSort 函数,该函数通过递归将数组分成更小的部分,然后合并这些部分的同时计算逆序对数量。

if (left >= right) return 0; 递归的基准情况,当区间只包含一个元素或为空时,逆序对数量为0。

int ret = 0; 初始化逆序对数量为0。

int mid = left + (right - left) / 2; 计算中点,用于分割数组。

ret += mergeSort(nums, left, mid); 递归计算左半部分的逆序对数量,并累加到 ret

ret += mergeSort(nums, mid + 1, right); 递归计算右半部分的逆序对数量,并累加到 ret

合并过程中计算逆序对

在合并两个已排序部分的过程中,当从右侧部分取出元素比左侧部分的当前元素小,意味着左侧部分当前元素及其后所有元素都与该右侧元素构成逆序对(因为左侧部分已排序)。

if (nums[cur1] <= nums[cur2]) { 如果左侧元素小于等于右侧元素,将左侧元素复制到 tmp

} else { 如果左侧元素大于右侧元素,计算逆序对数量(end1 - cur1 + 1),将右侧元素复制到 tmp

while 循环分别处理剩余的左侧和右侧元素,将它们复制到 tmp 中。

更新原数组

for (int i = left; i <= right; i++) 循环将 tmp 中的元素复制回原数组 nums 的相应位置。

时间复杂度和空间复杂度与归并排序一致。

结尾

最后,感谢您阅读我的文章,希望这些内容能够对您有所启发和帮助。如果您有任何问题或想要分享您的观点,请随时在评论区留言。

同时,不要忘记订阅我的博客以获取更多有趣的内容。在未来的文章中,我将继续探讨这个话题的不同方面,为您呈现更多深度和见解。

谢谢您的支持,期待与您在下一篇文章中再次相遇!

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

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

相关文章

前端学习-CSS基础-Day3

一、CSS三大特性 1.1层叠性 相同选择器给设置相同的样式&#xff0c;此时一个样式就会覆盖&#xff08;层叠&#xff09;另一个冲突的样式。层叠性主要解决样式冲突的问题 层叠性原则&#xff1a; 1.样式冲突&#xff0c;遵循的原则是就近原则&#xff0c;哪个样式离结构近&a…

深度解析 Partisia Blockchain 可证明的快速通道共识

来源&#xff1a; https://partisiablockchain.com/poseidon-provable-fast-track-consensus-by-partisia-blockchain/ https://drive.google.com/file/d/1OX7ljrLY4IgEA1O3t3fKNH1qSO60_Qbw/view 编译&#xff1a;TinTinLand 任何区块链的核心要求都是建立对共享分布式账本…

Web框架开发-Django中间件

一、中间件的概念 中间件顾名思义,是介于request与response处理之间的一道处理过程,相对比较轻量级,并且在全局上改变django的输入与输出。因为改变的是全局,所以需要谨慎实用,用不好会影响到性能。 Django的中间件的定义: Middleware is a framework of hooks into Dj…

软件工程学习笔记12——运行维护篇

运行维护篇 一、版本发布1、关于软件版本2、版本发布前&#xff0c;做好版本发布的规划3、规范好发布流程&#xff0c;保障发布质量 二、DevOps工程师1、什么是 DevOps 三、线上故障1、遇到线上故障&#xff0c;新手和高手的差距在哪里2、大厂都是怎么处理线上故障的 四、日志管…

【AI】在本地 Docker 环境中搭建使用 Hugging Face 托管的 Llama 模型

目录 Hugging Face 和 LLMs 简介利用 Docker 进行 ML格式的类型请求 Llama 模型访问创建 Hugging Face 令牌设置 Docker 环境快速演示访问页面入门克隆项目构建镜像运行容器结论推荐超级课程: Docker快速入门到精通Kubernetes入门到大师通关课AWS云服务快速入门实战Hugging Fa…

每日算法之接雨水

题目描述 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图&#xff0c;计算按此排列的柱子&#xff0c;下雨之后能接多少雨水。 示例 1&#xff1a; 输入&#xff1a;height [0,1,0,2,1,0,1,3,2,1,2,1] 输出&#xff1a;6 解释&#xff1a;上面是由数组 [0,1,0,2,1,0,1…

Zookeeper节点ZNode和相关属性

节点类型&#xff1a; Znode两种类型 &#xff1a; 持久的&#xff08;persistent&#xff09;&#xff1a;客户端和服务器端断开连接后&#xff0c;创建的节点不删除&#xff08;默认&#xff09;。 短暂的&#xff08;ephemeral&#xff09;&#xff1a;客户端和服务器端断…

OpenCV 如何使用 XML 和 YAML 文件的文件输入和输出

返回&#xff1a;OpenCV系列文章目录&#xff08;持续更新中......&#xff09; 上一篇&#xff1a;如何利用OpenCV4.9离散傅里叶变换 下一篇: 目标 本文内容主要介绍&#xff1a; 如何使用 YAML 或 XML 文件打印和读取文件和 OpenCV 的文本条目&#xff1f;如何对 OpenCV …

2023中国制造业标杆工厂数字化转型场景地图报告(可下载)

请前往星球下载&#xff08;可领取优惠券&#xff09;&#xff1a;https://t.zsxq.com/18gRm06pe

VsCode的json文件不允许注释的解决办法

右下角找到注释点进去 输入Files: Associations搜索出此项 改为项为*.json值为jsonc保存即可 然后会发现VsCode的json文件就允许注释了

[BT]BUUCTF刷题第10天(3.28)

第10天&#xff08;共3题&#xff09; Basic BUU SQL COURSE 1 打开网站看到右上角有个登录界面&#xff0c;怀疑是SQL注入 但是多次尝试都无果 通过看题解知道了还有一个隐藏网页&#xff08;content_detail.php&#xff09; 随便点一个测试新闻进去后点F12看网络&#xf…

SpringBoot+Prometheus+Grafana实现应用监控和报警

一、背景 SpringBoot的应用监控方案比较多&#xff0c;SpringBootPrometheusGrafana是目前比较常用的方案之一。它们三者之间的关系大概如下图&#xff1a; 关系图 二、开发SpringBoot应用 首先&#xff0c;创建一个SpringBoot项目&#xff0c;pom文件如下&#xff1a; <…

如何调试Clang源码

下载编译Clang 这个就直接去LLVM官网下载&#xff0c;然后编译好Clang就行&#xff0c;注意得debug模式&#xff0c;保存符号信息。 调试Clang 可以直接通过命令行来调试 #进入调试环境&#xff0c;这里的clang得是刚刚编译好的 lldb ./clang # r是运行&#xff0c;后面是正…

OpenHarmony实战开发-滑动容器组件Swiper的使用

介绍 本篇Codelab主要介绍了滑动容器组件Swiper的几种常见的应用场景&#xff0c;包括顶部导航、轮播图以及视频滑动播放。 相关概念 Swiper&#xff1a;滑动容器&#xff0c;提供子组件切换滑动的能力。Stack&#xff1a;堆叠容器&#xff0c;子组件按照顺序依次入栈&#x…

IC-随便记

1、移远通信---通信模组 物联网解决方案供应商&#xff0c;可提供完备的IoT产品和服务&#xff0c;涵盖蜂窝模组(5G/4G/3G/2G/LPWA)、车载前装模组、智能模组&#xff08;5G/4G/边缘计算&#xff09;、短距离通信模组(Wi-Fi&BT)、GNSS定位模组、卫星通信模组、天线等硬件产…

深圳区块链交易所app系统开发,撮合交易系统开发

随着区块链技术的迅速发展和数字资产市场的蓬勃发展&#xff0c;区块链交易所成为了数字资产交易的核心场所之一。在这个快速发展的领域中&#xff0c;区块链交易所App系统的开发和撮合交易系统的建设至关重要。本文将探讨区块链交易所App系统开发及撮合交易系统的重要性&#…

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

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

每日一题 --- 链表相交[力扣][Go]

链表相交 题目&#xff1a;面试题 02.07. 链表相交 给你两个单链表的头节点 headA 和 headB &#xff0c;请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点&#xff0c;返回 null 。 图示两个链表在节点 c1 开始相交**&#xff1a;** 题目数据 保证 整个链式结…

RabbitMQ3.x之四_RabbitMQ角色说明及创建用户与授权

RabbitMQ3.x之四_角色说明及创建用户与授权 文章目录 RabbitMQ3.x之四_角色说明及创建用户与授权1. 访问和授权1. Tags说明2. 命令行示例 2. 管理界面新建用户及访问授权1. 管理界面新建用户2. 管理界面中的授权说明3. guest用户不能远程登录提示 3. 创建用户1. 基本命令2. 实际…

【IP 组播】PIM-SM

目录 原理概述 实验目的 实验内容 实验拓扑 1.基本配置 2.配置IGP 3.配置PIM-SM 4.用户端DR与组播源端DR 5.从RPT切换到SPT 6.配置PIM-Silent接口 原理概述 PIM-SM 是一种基于Group-Shared Tree 的组播路由协议&#xff0c;与 PIM-DM 不同&#xff0c;它适合于组播组成…