【二十七】【算法分析与设计】归并(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://www.mzph.cn/news/777520.shtml

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

相关文章

Linux 系统 CentOS7 上搭建 Hadoop HDFS集群详细步骤

集群搭建 整体思路:先在一个节点上安装、配置,然后再克隆出多个节点,修改 IP ,免密,主机名等 提前规划: 需要三个节点,主机名分别命名:node1、node2、node3 在下面对 node1 配置时,先假设 node2 和 node3 是存在的 **注意:**整个搭建过程,除了1和2 步,其他操作都使…

Mini-Gemini: Mining the Potential of Multi-modality Vision Language Models

Mini-Gemini: Mining the Potential of Multi-modality Vision Language Models 相关链接&#xff1a;arxiv 关键字&#xff1a;Vision Language Models、Multi-modality、High-Resolution Visual Tokens、High-Quality Data、VLM-guided Generation 摘要 在这项工作中&#x…

unity学习(76)--窗口化和后台运行

1.通过如下方式将编译的游戏设置为窗口模式。 成功&#xff1a; 2.现在只有鼠标点击的窗体游戏运动&#xff0c;其他窗体游戏都会卡住。 2.1build setting中 2.2unity内部Project Settings 也被同步修改了

火车头通过关键词采集文章的原理

随着互联网信息的爆炸式增长&#xff0c;网站管理员和内容创作者需要不断更新和发布新的文章&#xff0c;以吸引更多的用户和提升网站的排名。而火车头作为一款智能文章采集工具&#xff0c;在这一过程中发挥着重要作用。本文将探讨火车头如何通过关键词采集文章&#xff0c;以…

CDH集群hive初始化元数据库失败

oracle数据库操作&#xff1a; 报错如下&#xff1a;命令 (Validate Hive Metastore schema (237)) 已失败 截图如下&#xff1a; 后台日志部分摘录&#xff1a; WARNING: Use “yarn jar” to launch YARN applications. SLF4J: Class path contains multiple SLF4J binding…

Vite 为什么比 Webpack 快?

目录 1. Webpack 的构建原理 2. Script 的模块化&#xff08;主流浏览器对 ES Modules 的支持&#xff09; 3. Webpack vs Vite 开发模式的差异 对 ES Modules 的支持 底层语言的差异 热更新的处理 1. Webpack 的构建原理 前端之所以需要类似于 Webpack 这样的构建工具&…

主机安全-德迅卫士

什么是主机安全&#xff1f; 主机安全&#xff0c;其核心内容包括安全应用交付系统、应用监管系统、操作系统安全增强系统和运维安全管控系统。它的具体功能是指保证主机在数据存储和处理的保密性、完整性&#xff0c;可用性&#xff0c;它包括硬件、固件、系统软件的自身安全&…

CMakeLists生成动态库.so和静态库.a

一、下载NDK CMake - NDK : 26.2.11394342 或 23.1.7779620 - CMake : 3.22.1 二、新建android\app\CMakeLists.txt 文件CMakeLists.txt内容 cmake_minimum_required(VERSION 3.4.1) #mker为项目名称 project(mker)#设置生成的so动态库最后输出的路径 set(CMAKE_LIBRARY_OUTP…

面试算法-122-翻转二叉树

题目 给你一棵二叉树的根节点 root &#xff0c;翻转这棵二叉树&#xff0c;并返回其根节点。 示例 1&#xff1a; 输入&#xff1a;root [4,2,7,1,3,6,9] 输出&#xff1a;[4,7,2,9,6,3,1] 解 class Solution {public TreeNode invertTree(TreeNode root) {return dfs(…

第二证券今日投资参考:低空经济迎利好 自动驾驶商业化提速

昨日&#xff0c;两市股指盘中弱势震动&#xff0c;午后加快下探&#xff0c;沪指失守3000点大关&#xff0c;深成指、创业板指跌超2%&#xff1b;到收盘&#xff0c;沪指跌1.26%报2993.14点&#xff0c;深成指跌2.4%报9222.47点&#xff0c;创业板指跌2.81%报1789.82点&#x…

DC电源模块的设计与制造流程

BOSHIDA DC电源模块的设计与制造流程 DC电源模块是一种用于将交流电转换为直流电的设备。它广泛应用于各种电子设备中&#xff0c;如电子产品、工业仪器、电视等。下面是DC电源模块的设计与制造流程的简要描述&#xff1a; 1. 需求分析&#xff1a;在设计DC电源模块之前&#…

设计模式(一)简介

一、书籍推荐及博客 大话设计模式 设计模式的艺术 XXL开源社区 | 博客 二、通俗版概念 创建型模式、结构型模式、行为型模式 怎么建房子、建什么样的房子、建的房子有什么用 三、重点模式及简述 1、创建型模式 工厂方法&#xff08;多态工厂的实现&#xff09; 抽象工厂…

JetBrains pycharm pro 2023 for mac Python集成开发环境

JetBrains PyCharm Pro 2023 for Mac是一款功能强大的Python集成开发环境&#xff08;IDE&#xff09;&#xff0c;专为Mac用户设计&#xff0c;旨在提供高效、智能的编程体验。 软件下载&#xff1a;JetBrains pycharm pro 2023 for mac中文最新版 PyCharm Pro 2023支持多种语…

上位机图像处理和嵌入式模块部署(qmacvisual图像修复)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 qmacvisual提供了一个图像修复的功能。所谓的图像修复&#xff0c;就是对图像中缺省的部分进行修补&#xff0c;它的操作&#xff0c;其实分成两个…

IDEA跑Java后端项目提示内存溢出

要设置几个地方&#xff0c;都试一下吧&#xff1a; 1、默认是700&#xff0c;我们设置大一点&#xff08;上次配置了这儿就解决了&#xff09; 2、 3、 4、-Xmx4g

葵花卫星影像应用场景及数据获取

一、卫星参数 葵花卫星是由中国航天科技集团公司研制的一颗光学遥感卫星&#xff0c;代号CAS-03。该卫星于2016年11月9日成功发射&#xff0c;位于地球同步轨道&#xff0c;轨道高度约为35786公里&#xff0c;倾角为0。卫星设计寿命为5年&#xff0c;搭载了高分辨率光学相机和多…

Cisco Firepower FMCv修改管理Ip方法

FMCv 是部署在VMWARE虚拟平台上的FMC 部署完成后&#xff0c;如何修改管理IP 1 查看当前版本 show version 可以看到是for VMware 2 修改管理IP步骤 2.1 进入expert模式 expert2.2 进入超级用户 sudo su并输入密码 2.3 查看当前网卡Ip 2.4 修改Ip 命令&#xff1a; /…

解码“零信任”,如何带来信任感?

零信任的“信任”来源&#xff0c;并非凭空而生&#xff0c;而是建立在严格、细致且持续的验证、策略之上。它不仅能够提升企业的安全防护能力&#xff0c;也在加速安全技术的创新与演进。 推动创新 零信任理念激活网络安全 身份和访问管理革新。零信任理念“永不信任&#…

Python 从0开始 一步步基于Django创建项目(13)将数据关联到用户

在city_infos应用程序中&#xff0c;每个城市信息条目是关联到城市的&#xff0c;所以只需要将城市条目关联到用户即可。 将数据关联到用户&#xff0c;就是把‘顶层’数据关联到用户。 设计思路&#xff1a; 1、修改顶层数据模型&#xff0c;向其中添加‘用户’属性 2、根…

寄主机显示器被快递搞坏了怎么办?怎么破?

大家好&#xff0c;我是平泽裕也。 最近&#xff0c;我在社区里看到很多关于开学后弟弟寄来的电脑显示器被快递损坏的帖子。 看到它真的让我感到难过。 如果有人的数码产品被快递损坏了&#xff0c;我会伤心很久。 那么今天就跟大家聊聊寄快递的一些小技巧。 作为一名曾经的…