分治算法(6)_归并排序_交易逆序对的总数

news2024/10/9 18:48:11

个人主页:C++忠实粉丝
欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 C++忠实粉丝 原创

分治算法(6)_归并排序_交易逆序对的总数

收录于专栏【经典算法练习】
本专栏旨在分享学习算法的一点学习笔记,欢迎大家在评论区交流讨论💌

目录

温馨提示: 

1. 题目链接:

2. 题目描述:

3. 解法:

算法思路:

方法一:快速统计出某个数前面有多少个数比它大(升序)

方法二:快速统计出某个数后面有多少个数比它小(降序)

代码展示:

结果分析:

 


温馨提示: 

这里需要有归并排序的相关知识, 如果对归并排序还不是很了解的宝子们, 可以先去下方链接进行查看:

分治算法(5)_归并排序_排序数组-CSDN博客 

1. 题目链接:

OJ链接 :  交易逆序对的总数

2. 题目描述:

在股票交易中,如果前一天的股价高于后一天的股价,则可以认为存在一个「交易逆序对」。请设计一个程序,输入一段时间内的股票交易记录 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

3. 解法:

算法思路:

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

 我们将这个问题分解成几个小问题, 逐一破解这道题.

为什么可以使用归并排序?

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

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

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

3. 逆序对中两个元素: 一个选左数组另一个选右数组 

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

示例: 

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

为什么要这么做? 

在归并排序合并过程中,  我们得到的是两个有序的数组. 我们是可以利用数组的有序性, 快速统计出逆序对的数量, 而不是将所有情况都枚举出来.

最核心的问题, 如何在合并两个有序数组的过程中, 统计出逆序对的数量?

合并两个有序序列时求逆序对的方法有两种:

1. 快速统计出某个数前面有多少个数比它大

2. 快速统计出某个数后面有多少数比它小

方法一:快速统计出某个数前面有多少个数比它大(升序)

通过一个示例来演示方法一:

假定已经有两个已经有序的序列以及辅助数组 left = [5, 7, 9] right = [4, 5, 8] help = [],通过合并两
个有序数组的过程,来求得逆序对的数量:
规定如下定义来叙述过程 :
cur1 遍历 left 数组,cur2 遍历 right 数组
ret 记录逆序对的数量

第⼀轮循环:
left[cur1] > right[cur2],由于两个数组都是升序的,那么我们可以断定,此刻 left 数组中
[cur1, 2] 区间内的 3 个元素均可与 right[cur2] 的元素构成逆序对,因此可以累加逆序对的数量 ret
+= 3,并且将 right[cur2] 加⼊到辅助数组中,cur2++ 遍历下⼀个元素。
第⼀轮循环结束后:left = [5, 7, 9] right = [x, 5, 8] help = [4] ret = 3 cur1 = 0 cur2 = 1

第⼆轮循环:
left[cur1] == right[cur2],因为 right[cur2] 可能与 left 数组中往后的元素构成逆序对,因此我
们需要将 left[cur1] 加⼊到辅助数组中去,此时没有产生逆序对,不更新 ret。
第⼆轮循环结束后:left = [x, 7, 9] right = [x, 5, 8] help = [4, 5] ret = 3 cur1 = 1 cur2 = 1

第三轮循环:
left[cur1] > right[cur2],与第⼀轮循环相同,此刻 left 数组中[cur1, 2] 区间内的 2 个元素均可
与 right[cur2] 的元素构成逆序对,更新 ret 的值为 ret += 2,并且将 right[cur2] 加⼊到辅助数组中
去,cur2++ 遍历下⼀个元素。
第三轮循环结束后:left = [x, 7, 9] right = [x, x, 8] help = [4, 5, 5] ret = 5 cur1 = 1 cur2 = 2

第四轮循环:
left[cur1] < right[cur2],由于两个数组都是升序的,因此我们可以确定 left[cur1] 比 right 数
组中的所有元素都要小。left[cur1] 这个元素是不可能与 right 数组中的元素构成逆序对。因此,⼤胆的将 left[cur1] 这个元素加入到辅助数组中去,不更细 ret 的值。
第四轮循环结束后:left = [x, x, 9] right = [x, x, 8] help = [4, 5, 5, 7] ret = 5 cur1 = 2 cur2 = 2

第五轮循环:
left[cur1] > right[cur2],与第⼀、第三轮循环相同。此时 left 数组内的 1 个元素能与
right[cur2] 构成逆序对,更新 ret 的值,并且将 right[cur2] 加⼊到辅助数组中去。
第五轮循环结束后:left = [x, x, 9] right = [x, x, x] help = [4, 5, 5, 7, 8] ret = 6 cur1 = 2 cur2 = 2


处理剩余元素:
    • 如果是左边出现剩余,说明左边剩下的所有元素都是比右边元素⼤的,但是它们都是已经被计算过的(我们以右边的元素为基准的),因此不会产生逆序对,仅需归并排序即可。
    • 如果是右边出现剩余,说明右边剩下的元素都是比左边大的,不符合逆序对的定义,因此也不需要处理,仅需归并排序即可
    

整个过程只需将两个数组遍历⼀遍即可,时间复杂度为 O(N)。
由上述过程我们可以得出方法⼀统计逆序对的关键点:
在合并有序数组的时候,遇到左数组当前元素 > 右数组当前元素时,我们可以通过计算左数组中剩余元素的长度,就可快速求出右数组当前元素前面有多少个数比它大,对比解法一中一个一个枚举逆序对效率快了许多。

 

 

方法二:快速统计出某个数后面有多少个数比它小(降序)

依旧通过一个示例来演示方法二:
假定已经有两个已经有序的序列以及辅助数组 left = [5, 7, 9] right = [4, 5, 8] help = [],通过合并两
个有序数组的过程,来求得逆序对的数量:
规定如下定义来叙述过程 :
cur1 遍历 left 数组,cur2 遍历 right 数组
ret 记录逆序对的数量

第⼀轮循环:
left[cur1] > right[cur2],先不要着急统计,因为我们要找的是当前元素后⾯有多少比它小的,
这里虽然出现了一个,但是 right 数组中依旧还可能有其余比它小的。因此此时仅将 right[cur2] 加⼊
到辅助数组中去,并且将 cur2++。
第⼀轮循环结束后:left = [5, 7, 9] right = [x, 5, 8] help = [4] ret = 0 cur1 = 0 cur2 = 1
第⼆轮循环:
left[cur1] == right[cur2],由于两个数组都是升序,这个时候对于元素 left[cur1] 来说,我们已
经可以断定 right 数组中[0, cur2) 左闭右开区间上的元素都是比它小的。因此此时可以统计逆序对的数量 ret += cur2 - 0,并且将 left[cur1] 放⼊到辅助数组中去,cur1++ 遍历下⼀个元素。
第二轮循环结束后:left = [x, 7, 9] right = [x, 5, 8] help = [4, 5] ret = 1 cur1 = 1 cur2 = 1

第三轮循环:
left[cur1] > right[cur2],与第⼀轮循环相同,直接将 right[cur2] 加⼊到辅助数组中去,
cur2++ 遍历下⼀个元素。
第三轮循环结束后:left = [x, 7, 9] right = [x, x, 8] help = [4, 5, 5] ret = 1 cur1 = 1 cur2 = 2

第四轮循环:
left[cur1] < right[cur2],由于两个数组都是升序的,这个时候对于元素 left[cur1] 来说,我们
依旧已经可以断定 right 数组中[0, cur2) 左闭右开区间上的元素都是比它小的。因此此时可以统计逆序对的数量 ret += cur2 - 0,并且将 left[cur1] 放⼊到辅助数组中去,cur1++ 遍历下⼀个元素。
第四轮循环结束后:left = [9] right = [8] help = [4, 5, 5, 7] ret = 3 cur1 = 2 cur2 = 2

第五轮循环:
left[cur1] > right[cur2],与第⼀、第三轮循环相同。直接将 right[cur2] 加⼊到辅助数组中去,
cur2++ 遍历下⼀个元素。
第五轮循环结束后:left = [x, x, 9] right = [x, x, x] help = [4, 5, 5, 7, 8] ret = 3 cur1 = 2 cur2 = 2

处理剩余元素:
• 如果是左边出现剩余,说明左边剩下的所有元素都是比右边元素大的,但是相比较于方法一,逆序对的数量是没有统计过的。因此,我们需要统计 ret 的值:
◦ 设左边数组剩余元素的个数为 leave
◦ ret += leave * (cur2 - 0)
对于本题来说,处理剩余元素的时候, left 数组剩余 1 个元素,cur2 - 0 = 3,因此 ret 需要类加
上 3,结果为 6。与方法一求得的结果相同。
• 如果是右边出现剩余,说明右边剩下的元素都是⽐左边⼤的,不符合逆序对的定义,因此也不需要处理,仅需归并排序即可。

整个过程只需将两个数组遍历⼀遍即可,时间复杂度依旧为 O(N)。

由上述过程我们可以得出方法二统计逆序对的关键点:
在合并有序数组的时候,遇到左数组当前元素 <= 右数组当前元素时,我们可以通过计算右数组已经遍历过的元素的长度,快速求出左数组当前元素后面有多少个数比它大。
但是需要注意的是,在处理剩余元素的时候,方法二还需要统计逆序对的数量。

 

代码展示:

升序:

class Solution 
{
    int tmp[50010];
public:
    int reversePairs(vector<int>& record) {
        return mergesort(record, 0, record.size() - 1);
    }

    int mergesort(vector<int>& nums, int left, int right)
    {
        if(left >= right) return 0;

        int ret = 0;
        //找中间点, 将数组分成两部分
        int mid = (left + right) >> 1;
        
        //左边的个数 + 排序 + 右边的个数 + 排序
        ret += mergesort(nums, left, mid) + mergesort(nums, mid + 1, right);
        
        //一左一右的个数
        int cur1 = left, cur2 = mid + 1, i = 0;
        while(cur1 <= mid && cur2 <= right)
        {
            if(nums[cur1] <= nums[cur2]) tmp[i++] = nums[cur1++]; 
            else ret += mid - cur1 + 1, tmp[i++] = nums[cur2++];
        }

        //处理排序
        while(cur1 <= mid) tmp[i++] = nums[cur1++];
        while(cur2 <= right) tmp[i++] = nums[cur2++];
        for(int j = left; j <= right; j++)
            nums[j] = tmp[j - left];

        return ret;
    }
};

 

降序:

class Solution 
{
    int tmp[50010];
public:
    int reversePairs(vector<int>& record) {
        return mergesort(record, 0, record.size() - 1);
    }

    int mergesort(vector<int>& nums, int left, int right)
    {
        if(left >= right) return 0;

        int ret = 0;
        //找中间点, 将数组分成两部分
        int mid = (left + right) >> 1;
        
        //左边的个数 + 排序 + 右边的个数 + 排序
        ret += mergesort(nums, left, mid) + mergesort(nums, mid + 1, right);
        
        //一左一右的个数
        int cur1 = left, cur2 = mid + 1, i = 0;
        while(cur1 <= mid && cur2 <= right)
        {
            if(nums[cur1] <= nums[cur2]) tmp[i++] = nums[cur2++]; 
            else ret += right - cur2 + 1, tmp[i++] = nums[cur1++];
        }

        //处理排序
        while(cur1 <= mid) tmp[i++] = nums[cur1++];
        while(cur2 <= right) tmp[i++] = nums[cur2++];
        for(int j = left; j <= right; j++)
            nums[j] = tmp[j - left];

        return ret;
    }
};

 

结果分析:

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

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

相关文章

怎么去掉图片上的文字不留痕迹?学会这5种P图方法轻松解决

图片编辑已成为我们日常生活和工作中不可或缺的一部分。但有时候&#xff0c;图片上的一些文字却成了我们分享或使用的障碍。如何无痕去除图片上的文字呢&#xff1f;今天&#xff0c;我将为大家介绍5种高效工具&#xff0c;让你轻松P图&#xff0c;一起来学习下吧。 工具一&am…

ESP32利用WebServer进行设备配置

目标需求 利用esp32的WebServer功能&#xff0c;展示一个网页&#xff0c;对里面的参数进行配置&#xff0c;并以json文本格式保存到flash里面。 1、定义HTML const char index_html[] PROGMEM R"rawliteral( <!DOCTYPE html> <html lang"en"> …

前沿论文 M5Product 组会 PPT

对比学习&#xff08;Contrast learning&#xff09;&#xff1a;对比学习是一种自监督学习方法&#xff0c;用于在没有标签的情况下&#xff0c;通过让模型学习哪些数据点相似或不同来学习数据集的一般特征。假设一个试图理解世界的新生婴儿。在家里&#xff0c;假设有两只猫和…

PPT在线画SWOT分析图!这2个在线软件堪称办公必备!

swot分析ppt怎么做&#xff1f; swot分析是一个非常常用的战略分析框架&#xff0c;经常会在ppt中使用。想在ppt中绘制swot分析图&#xff0c;使用自带的形状工具可以制作出来&#xff0c;但绘制效率不够高&#xff0c;在需要大批量制作的场景下&#xff0c;会让人非常心累………

【WebGis开发 - Cesium】三维可视化项目教程---初始化场景

系列文章目录 【WebGis开发 - Cesium】三维可视化项目教程—视点管理 目录 系列文章目录引言一、Cesium引入项目1.1 下载资源1.2 项目引入Cesium 二、初始化地球2.1 创建基础文件2.1.1 创建Cesium工具方法文件2.1.2 创建主页面 2.2 看下效果 三、总结 引言 本教程主要是围绕Ce…

现场直击!2023望繁信科技产品发布会精彩回顾

2023望繁信科技产品发布会圆满结束。 感谢200余名企业代表、合作伙伴、媒体到场参会&#xff0c;感谢3万多位关注望繁信科技和流程挖掘的朋友在线观看直播。 在会上&#xff0c;我们正式分享了望繁信科技多年深耕流程挖掘领域的思考、积累和部署&#xff0c;发布了过去一年在…

Pyppeteer:如何在 Python 中使用 Puppeteer 和 Browserless?

Python 中的 Pyppeteer 是什么&#xff1f; Pyppeteer 是流行的 Node.js 库 Puppeteer 的 Python 移植版本&#xff0c;用于以编程方式控制无头 Chrome 或 Chromium 浏览器。 本质上&#xff0c;Pyppeteer 允许 Python 开发人员在 Web 浏览器中自动执行任务&#xff0c;例如抓…

webm格式怎么转换成mp4?值得给你推荐的几种简单方法

webm格式怎么转换成mp4&#xff1f;MP4支持多种音频和视频编解码器&#xff0c;如H.264和AAC&#xff0c;用户可以根据需要调整视频和音频质量&#xff0c;以满足不同需求。同时&#xff0c;许多视频编辑软件广泛支持MP4格式&#xff0c;使得剪辑、合成和特效处理变得更加便捷。…

人工智能、人机交互和机器人国际学术会议

第三届人工智能、人机交互和机器人国际学术会议 &#xff08;AIHCIR 2024&#xff09;组委会热忱地邀请您参与本届大会。本届大会旨在聚集领先的科学家、研究人员和学者&#xff0c;共同交流和分享在人工智能、人机交互和机器人各个方面的经验和研究成果&#xff0c;为研究人员…

【C++】模板(初识):函数模板、类模板

本篇主要介绍C中的模板初阶的一些知识。模板分为函数模板和类模板&#xff0c;我们一个一个来看。 1.函数模板 1.1函数模板概念 函数模板代表了一个函数家族&#xff0c;该函数模板与类型无关&#xff0c;在使用时被参数化&#xff0c;根据实际的参数类型产生函数特定版本。…

LSTM时间序列模型实战——预测上证指数走势

LSTM时间序列模型实战——预测上证指数走势 关于作者 作者&#xff1a;小白熊 作者简介&#xff1a;精通python、matlab、c#语言&#xff0c;擅长机器学习&#xff0c;深度学习&#xff0c;机器视觉&#xff0c;目标检测&#xff0c;图像分类&#xff0c;姿态识别&#xff0c;…

五款软件助你秒变职场达人

✨ 每天忙碌于工作&#xff0c;却感觉事半功倍&#xff1f;别担心&#xff0c;今天就为大家揭秘5款高效工作软件&#xff0c;让你秒变职场达人&#xff0c;效率直线飙升&#xff01;&#x1f389; 1️⃣ 亿可达 &#x1f3d7;️ 软件连接神器 &#x1f3af; 特点&#xff1a…

Java后端面试很水的,7天就能搞定!

随着Java的越来越卷&#xff0c;面试也直接上难度了&#xff0c;从以前的八股文到场景题了&#xff0c;尤其是有经验的去面试&#xff0c;场景题都是会问的&#xff0c;近期面试过的应该都深有体会&#xff01; 场景题230道&#xff1a; 1.分布式锁加锁失败后的等待逻辑是如何…

项目管理全流程包括哪些环节,一文读懂项目管理全流程

项目管理全流程是一个系统性、阶段性的过程&#xff0c;旨在确保项目从启动到完成的高效运行。该流程包括以下几个关键阶段&#xff1a;项目管理全流程是确保项目从启动到完成顺利进行的一系列阶段和活动的总称。 以下是基于项目管理全流程对项目管理的描述&#xff1a; 一、…

单路测径仪的详细介绍

蓝鹏测控单路测径仪是一种高精度、高效率的在线检测设备&#xff0c;广泛应用于线缆、电缆、电线、胶管、塑料管、金属丝等行业的生产过程中。以下是对该产品的详细介绍&#xff1a; 一、核心技术与工作原理 蓝鹏测控单路测径仪以光电检测为核心&#xff0c;结合单片机、LED灯、…

Qt-QStatusBar窗口状态栏相关操作(48)

目录 描述 使用 设置临时消息 添加子控件 添加 Labe 添加 进度条 从右边添加 按钮 描述 状态栏是应⽤程序中输出简要信息的区域。⼀般位于主窗⼝的最底部&#xff0c;⼀个窗⼝中最多只能有⼀个状态栏。在 Qt 中&#xff0c;状态栏是通过 QStatusBar类 来实现的。在状态…

Linux-处理用户输入

命令行参数 特殊参数变量 移动变量 处理选项-查找选项 处理选项-使用getopt命令 获得用户输入

讲座在线预约管理系统的设计与实现使用SpringBootSSM框架开发

目录 摘要 1 引言 2 系统需求分析 3 技术选型 4 系统架构设计 5 核心功能实现 5.1 用户管理 5.2 讲座管理 5.3 预约管理 5.4 评论系统 6 安全性考虑 7 测试 8 结论 摘要 本文旨在设计和实现一个基于Spring Boot SSM框架的讲座在线预约管理系统&#xff0c;并结合…

【Linux进程间通信】深入探索:Linux下的命名管道与System V共享内存

&#x1f4dd;个人主页&#x1f339;&#xff1a;Eternity._ ⏩收录专栏⏪&#xff1a;Linux “ 登神长阶 ” &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; ❀Linux进程间通信 &#x1f4d2;1. 命名管道&#x1f4d9;2. 命名管道实现server&client通…

环境变量及命令行参数

目录 一、环境变量的概念和基本命令 二、环境变量的组织结构及获取环境变量的方式 &#xff08;1&#xff09;组织结构 &#xff08;2&#xff09;获取环境变量 命令行第三个参数 通过第三方变量environ获取 通过系统调用getenv获取 三、命令行参数 一、环境变量的概念和…