《算法题讲解指南:优选算法-分治-归并》--47.归并排序,48.数组中的逆序对

《算法题讲解指南:优选算法-分治-归并》--47.归并排序,48.数组中的逆序对

🔥小叶-duck个人主页

❄️个人专栏《Data-Structure-Learning》

《C++入门到进阶&自我学习过程记录》《算法题讲解指南》--优选算法

未择之路,不须回头
已择之路,纵是荆棘遍野,亦作花海遨游


目录

47.归并排序

题目链接:

题目描述:

题目示例:

解法(归并排序):

算法思路:

C++算法代码:

算法总结及流程解析:

48.数组中的逆序对

题目链接:

题目描述:

题目示例:

解法(利用归并排序的过程——分治):

算法思路:

C++算法代码:

算法总结及流程解析:

结束语


47.归并排序

题目链接:

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

题目描述:

题目示例:

解法(归并排序):

算法思路:

      归并排序的流程充分的体现了「分而治之」的思想,大体过程分为两步:

      分:将数组一分为二为两部分,一直分解到数组的长度为1,使整个数组的排序过程被分为「左半部分排序」+「右半部分排序」;
      治:将两个较短的「有序数组合并成一个长的有序数组」,一直合并到最初的长度。

C++算法代码:

class Solution { public: //归并排序的算法 vector<int> tmp; //用于存放两个有序数组合并后的结果 void mergesort(vector<int>& nums, int left, int right) { if(left == right) { return; } //1、选择中间点划分区间 int mid = (right - left) / 2 + left; //将数组分成两块:[left, mid] [mid + 1, right] //2、把左右区间排序 mergesort(nums, left, mid); mergesort(nums, mid + 1, right); //3、将两个数组合并成一个有序数组 int cur1 = left, cur2 = mid + 1, i = 0; while(cur1 <= mid && cur2 <= right) { tmp[i++] = nums[cur1] <= nums[cur2] ? nums[cur1++] : nums[cur2++]; } //还有一边数组没有合并完 while(cur1 <= mid) { tmp[i++] = nums[cur1++]; } while(cur2 <= right) { tmp[i++] = nums[cur2++]; }//只会进其中一个循环 //将两个数组有序合并到tmp中后,再还原给原数组nums对应部分位置 for(int i = left; i <= right; i++) { nums[i] = tmp[i - left]; //tmp数组每次都是以开头下标0的位置合并两个数组 } } vector<int> sortArray(vector<int>& nums) { //归并实现: tmp.resize(nums.size()); mergesort(nums, 0, nums.size() - 1); return nums; } };

算法总结及流程解析:

48.数组中的逆序对

题目链接:

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

题目描述:

题目示例:

解法(利用归并排序的过程——分治):

算法思路:

      ⽤归并排序求逆序数是很经典的⽅法,主要就是在归并排序的合并过程中统计出逆序对的数量,也就是在合并两个有序序列的过程中,能够快速求出逆序对的数量。

      我们将这个问题分解成⼏个⼩问题,逐⼀破解这道题。
      (注意:默认都是升序,如果掌握升序的话,降序的归并过程也是可以解决问题的。)

      先解决第⼀个问题,为什么可以利⽤归并排序?

      如果我们将数组从中间划分成两个部分,那么我们可以将逆序对产⽣的⽅式划分成三组:

      逆序对中两个元素:全部从左数组中选择

      逆序对中两个元素:全部从右数组中选择

      逆序对中两个元素:⼀个选左数组另⼀个选右数组

      根据排列组合的分类相加原理,三种种情况下产⽣的逆序对的总和,正好等于总的逆序对数量。

      ⽽这个思路正好匹配归并排序的过程:

      先排序左数组;

      再排序右数组;

      左数组和右数组合⼆为⼀。

      因此,我们可以利⽤归并排序的过程,先求出左半数组中逆序对的数量,再求出右半数组中逆序对的数量,最后求出⼀个选择左边,另⼀个选择右边情况下逆序对的数量,三者相加即可。

      解决第⼆个问题,为什么要这么做?

      在归并排序合并的过程中,我们得到的是两个有序的数组。我们是可以利⽤数组的有序性,快速统计出逆序对的数量,⽽不是将所有情况都枚举出来。• 最核⼼的问题,如何在合并两个有序数组的过程中,统计出逆序对的数量?合并两个有序序列时求逆序对的⽅法有两种:

1. 快速统计出某个数前⾯有多少个数⽐它⼤;

2. 快速统计出某个数后⾯有多少个数⽐它⼩;

C++算法代码:

class Solution { public: int count = 0; vector<int> tmp;//用于存放两个有序数组合并后的结果 int reversePairs(vector<int>& record) { tmp.resize(record.size()); mergesort(record, 0, record.size() - 1); return count; } void mergesort(vector<int>& nums, int left, int right) { if(left >= right)//正常归并排序递归的结束条件不用包含left>right //因为归并是分两块,最小的情况就是两个数分成各一个,不存在越界的情况 //但是此题有空数组的案例,所以一开始left=0,right=-1,要考虑在内 { return; } //1、选择中间点划分区间 int mid = (right - left) / 2 + left; //将数组分成两块:[left, mid] [mid + 1, right] //2、把左右区间排序 mergesort(nums, left, mid); mergesort(nums, mid + 1, right); //3、判断两个有序数组一左一右的逆序对个数,并且将两个数组合并成一个有序数组 int cur1 = left, cur2 = mid + 1, i = 0; //(1)将数组排成升序的思路: while(cur1 <= mid && cur2 <= right) { if(nums[cur1] <= nums[cur2]) { tmp[i++] = nums[cur1++]; } else { //当第一次遇见nums[cur1] > nums[cur2],说明[cur1, mid]区间所有值都大于nums[cur2] //计算当前nums[cur2]的逆序对个数 count += mid - cur1 + 1; tmp[i++] = nums[cur2++]; } } //(2)将数组排成降序的思路: // while(cur1 <= mid && cur2 <= right) // { // if(nums[cur1] > nums[cur2]) // { // count += right - cur2 + 1; // tmp[i++] = nums[cur1++]; // } // else // { // tmp[i++] = nums[cur2++]; // } // } //处理还没有排序的剩余部分 while(cur1 <= mid) { tmp[i++] = nums[cur1++]; } while(cur2 <= right) { tmp[i++] = nums[cur2++]; } //将两个数组有序合并到tmp中后,再还原给原数组nums对应部分位置 for(int i = left; i <= right; i++) { nums[i] = tmp[i - left]; //tmp数组每次都是以开头下标0的位置合并两个数组 } } };

算法总结及流程解析:

结束语

      到此,47.归并排序,48.数组中的逆序对 这两道算法题就讲解完了。 归并排序采用分治思想,先将数组不断二分至单个元素,再通过有序合并操作完成排序,时间复杂度为O(nlogn)。希望大家能有所收获!

Read more

Flutter 三方库 objectbox_generator — 自动化构建鸿蒙极速 NoSQL 数据库映射(适配鸿蒙 HarmonyOS Next ohos)

Flutter 三方库 objectbox_generator — 自动化构建鸿蒙极速 NoSQL 数据库映射(适配鸿蒙 HarmonyOS Next ohos)

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net。 Flutter 三方库 objectbox_generator — 自动化构建鸿蒙极速 NoSQL 数据库映射(适配鸿蒙 HarmonyOS Next ohos) 在高性能移动应用开发中,本地数据的持久化存储效率往往是决定用户感知流畅度的木桶短板。传统的 SQLite 虽然结构化程度高,但在处理大规模对象关系映射(ORM)时,复杂的 SQL 拼接和反射解析往往会成为性能瓶颈。 ObjectBox 作为一个专为移动设备打造的、跨平台的超高速 NoSQL 数据库,已经成为了许多追求极致体验开发者的首选。而在 Flutter for OpenHarmony 开发中,配合 objectbox_generator,我们可以通过注解驱动的自动化流程,掌握这套高性能数据库的核心用法。 ⚠️ 鸿蒙适配现状提示:截至本文撰写时,ObjectBox 的 Dart 插件尚未提供官方的 OpenHarmony

By Ne0inhk
Spring Cloud 熔断降级详解:用 “保险丝“ 类比,Sentinel 实战教程

Spring Cloud 熔断降级详解:用 “保险丝“ 类比,Sentinel 实战教程

欢迎文末添加好友交流,共同进步! “ 俺はモンキー・D・ルフィ。海贼王になる男だ!” * 📋 目录 * 什么是熔断降级 * 定义 * 为什么需要熔断降级? * 保险丝类比:形象理解熔断机制 * 生活中的保险丝 * 熔断器工作原理对比 * 熔断器三种状态 * Sentinel 核心概念 * 什么是 Sentinel? * 核心概念对比 * Sentinel vs Hystrix 对比 * Sentinel 实战教程 * 环境准备 * 1. 添加依赖 * 2. 配置文件 * 基础示例:注解方式 * 3. 主启动类 * 4. 创建订单服务 * 5. 控制器 * 高级配置:规则定义 * 6. 流控规则配置 * OpenFeign 集成 * 7. Feign客户端集成Sentinel * 8. Feign降级处理 * 规则持久化(

By Ne0inhk
RUST异步微服务架构的最佳实践与常见反模式

RUST异步微服务架构的最佳实践与常见反模式

RUST异步微服务架构的最佳实践与常见反模式 一、项目优化前的问题分析 1.1 任务调度不合理 💡在第21篇项目中,用户同步服务的任务调度使用了Cron调度器,但Cron调度器的精度有限,可能导致任务执行延迟。此外,任务的并发度没有配置,可能导致任务积压。 1.2 I/O资源限制不足 订单处理服务的TCP连接队列大小没有配置,可能导致连接失败。数据库连接池的大小没有配置,可能导致数据库连接耗尽。 1.3 同步原语使用不当 实时监控服务中,Redis连接没有使用连接池,可能导致连接开销过大。任务结果的处理没有使用批量操作,可能导致上下文切换过多。 1.4 错误处理不完善 任务失败的处理逻辑不够完善,没有进行任务重试和错误统计。服务之间的通信没有进行超时管理和错误处理。 二、异步架构设计模式的应用 2.1 命令查询分离(CQS) CQS是一种架构设计模式,将系统的操作分为命令和查询两种类型。命令用于修改系统状态,查询用于获取系统状态,两者互不干扰。 在项目中,我们可以将用户同步任务视为命令操作,将系统状态查询视为查询操作: // 用户同步任务(

By Ne0inhk
新能源汽车电子架构革命:深度解析AUTOSAR标准与实践

新能源汽车电子架构革命:深度解析AUTOSAR标准与实践

新能源汽车电子架构革命:深度解析AUTOSAR标准与实践(附完整技术图谱) 引言:软件定义汽车时代的破局之道 在特斯拉FSD芯片算力突破72TOPS、华为ADS 2.0实现城市高阶智驾的今天,一场围绕汽车"大脑"的战争正在悄然打响。传统分布式电子架构已逼近物理极限,而集中式EE架构的进化离不开底层软件的革新——这就是AUTOSAR标准诞生的时代背景。本文将从技术原理、工程实践、未来趋势三个维度,为您揭开智能汽车灵魂的神秘面纱。 目录 * 第一章 AUTOSAR的前世今生:汽车软件革命的序章 * 第二章 技术解密:AUTOSAR的三层架构精要 * 第三章 工程实践:AUTOSAR落地全流程详解 * 第四章 进阶应用:新能源汽车场景实践 * 第五章 未来趋势:AUTOSAR的进化之路 * 结语:站在软件定义汽车的十字路口 第一章 AUTOSAR的前世今生:汽车软件革命的序章 1.1 行业困局:当摩尔定律遇见机械工业 (插入图表:2010-2025年汽车ECU数量增长曲线) 传统架构痛点解析: 硬件依赖症:

By Ne0inhk