《算法题讲解指南:优选算法-分治-归并》--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

【数据结构与算法】21.合并两个有序链表(LeetCode)

【数据结构与算法】21.合并两个有序链表(LeetCode)

文章目录 * 合并两个有序链表:高效算法解析与实现 * 问题描述 * 核心思路:双指针尾插法 * 完整代码实现 * 关键点解析 * 1. 边界条件处理 * 2. 头节点初始化 * 3. 节点比较与插入 * 4. 剩余节点处理 * 常见错误与修正 * 优化方案:哨兵节点 * 算法应用场景 * 总结 * 总结 合并两个有序链表:高效算法解析与实现 链表合并是数据结构中的经典问题,在算法面试和实际开发中经常出现。本文将深入解析如何高效合并两个有序链表,并展示C语言的实现方案。 问题描述 给定两个升序排列的链表list1和list2,要求将它们合并为一个新的升序链表并返回。新链表应该通过拼接给定链表的节点来完成。 示例: 输入:list1 = [1,2,4], list2 = [1,3,4] 输出:[1,1,2,3,4,4] 核心思路:

By Ne0inhk

【学习记录】使用 John the Ripper 和 Hashcat破解 RAR、ZIP 与 7z 文件密码(Windows教程)

文章目录 * 📌 引言 * 📦 支持类型 * 🛠️ 所需工具下载地址(Windows 官方编译版) * 额外工具 * 🔐 一、破解RAR 压缩文件密码 * 步骤 1:获取RAR 文件的加密哈希值 * 步骤 2:将正确的哈希值复制到 `hash.txt` 文件 * 步骤 3:在 Hashcat 官方 wiki 查找 `-m` 哈希模式 ID * 步骤 4: 使用 Hashcat 破解哈希值 * 📁 二、破解ZIP 压缩文件密码 * 步骤 1:提取 ZIP 文件的加密哈希值 * 步骤 2:将正确的哈希值复制到 `hash.txt` 文件 * 步骤

By Ne0inhk
Flutter 三方库 collection — 鸿蒙应用全方位集合操作与算法增强利器,实现鸿蒙深度适配下的高效容器过滤与优先级队列实战全解析(适配鸿蒙 HarmonyOS Next ohos)

Flutter 三方库 collection — 鸿蒙应用全方位集合操作与算法增强利器,实现鸿蒙深度适配下的高效容器过滤与优先级队列实战全解析(适配鸿蒙 HarmonyOS Next ohos)

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net。 Flutter 三方库 collection — 鸿蒙应用全方位集合操作与算法增强利器,实现鸿蒙深度适配下的高效容器过滤与优先级队列实战全解析 前言 在鸿蒙(OpenHarmony)应用开发中,数据结构的选择往往决定了逻辑的成败。当标准的 List、Set、Map 无法满足更高级的需求(例如:需要一个自动按优先级排序的任务队列,或者需要判断两个深度嵌套的 Map 是否完全一致)时,开发者就需要引入更强大的集合支持。 collection 是 Dart 官方维护的最核心基础库之一。它不仅补充了大量缺失的容器类型(如 PriorityQueue、Heap),还为原生集合提供了极其丰富的扩展工具类(如 ListEquality、CanonicalizedMap)。在 Flutter for OpenHarmony 的底层架构实践中,它是处理复杂业务逻辑、优化检索效率的必备“基石”。 一、原理解析 / 概念介绍

By Ne0inhk
【数据结构】常见时间复杂度以及空间复杂度

【数据结构】常见时间复杂度以及空间复杂度

时间复杂度与空间复杂度 * 一、复杂度的概念 * 二、时间复杂度 * 1、大O的渐进表示法 * 2、函数clock计算运算时间 * 3、常见复杂度对比 * 3.1常数项复杂度 * 3.2线性时间复杂度 * 案例1 * 案例2 * 3.3平方阶复杂度 * 3.4对数复杂度 * 3.5递归函数 * 单递归 * 双递归 * 三、空间复杂度 * 冒泡排序O(1) * 三个反置O(N) 一、复杂度的概念 * 一个算法的好坏,主要是对比两者的时间和空间两个维度,也就是时间和空间复杂度。 * 时间复杂度主要衡量一个算法运行的快慢,空间复杂度主要衡量一个算法运行需要的额外空间 二、时间复杂度 * 算法的时间复杂度是一个函数式T(N),算法中的基本操作的执行次数,为算法的时间复杂度。 * 注:编译器的不同,编译所需要的时间也不同。越新的编译器,编译的时间往往比旧的编译器快 * 当一个算法函数式为T(

By Ne0inhk