【算法专题】归并排序

news2024/9/23 1:26:14

目录

1. 排序数组

2. 交易逆序对的总数

3. 计算右侧小于当前元素的个数

4. 翻转对

总结


1. 排序数组

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

        今天我们使用归并排序来对数组进行排序,实际上,归并排序和快速排序是有一定相似之处的,都运用了分而治之的思想提升了排序效率。快速排序的实现思路是每次排序把区间划分为小于基准元素、等于基准元素、大于基准元素三个部分,直至数组整体有序为止;而归并排序的实现思路则是每次排序把区间平均划分为两个部分,分别对这两个部分再次排序,然后把这两个部分合并,重复这个过程直至子数组为一。

        显然合并数组这个操作是需要一个数组进行辅助的,由于归并排序过程中两个相等的元素在数组中的位置不会发生改变,所以这是一个稳定的排序算法,虽然在不要求稳定的情况下,都是快速排序比归并排序更快,但归并排序也有自己的应用场景,这点我们在后面会提到。

       

class Solution {
public:
    vector<int> temp;
    vector<int> sortArray(vector<int>& nums) 
    {
        temp.resize(nums.size());
        mergesort(nums, 0, nums.size() - 1);
        return nums;
    }
    void mergesort(vector<int> &nums, int left, int right)
    {
        if(left >= right) return;
        int mid = (left + right) >> 1;
        mergesort(nums, left, mid);
        mergesort(nums, mid + 1, right);
        int cur1= left, cur2 = mid + 1, i = 0;
        while(cur1 <= mid && cur2 <= right)
        {
            temp[i++] = (nums[cur1] <= nums[cur2]) ? nums[cur1++] : nums[cur2++];
        }
        while(cur1 <= mid) temp[i++] = nums[cur1++];
        while(cur2 <= right) temp[i++] = nums[cur2++];
        for(int j = left; j <= right; j++)
        {
            nums[j] = temp[j - left];
        }
    }
};

2. 交易逆序对的总数

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

        依据题意,我们需要求出一个数组中的逆序对总数,逆序对的定义是前面的数大于后面的数时,这两个数可以组成逆序对。首先能想到的肯定是暴力枚举,两层for循环列举出所有符合条件的逆序对情况,但既然这是困难题,暴力枚举法肯定是通过不了的,所以我们要想办法对暴力法做出优化。

        首先,如果我们把数组平均分为左右两个部分,那么要查找逆序对的步骤就是在左半部分找逆序对、在右半部分找逆序对、左右部分各取一个数,找逆序对。这样一来,就能找出所有满足条件的逆序对了,这时大家可能就会奇怪了,这不还是相当于枚举吗?确实是这样,但如果我们在找完左半部分逆序对后对左边进行排序、找完右半部分逆序对后对右边进行排序、在找完左右部分的逆序对后对数组整体进行排序,大家可能发现了,这样一来我们就能够用归并排序来对求逆序对的流程进行优化了。

        为什么说排序能够优化查找逆序对的效率呢?我举个例子大家就明白了。

大家可以发现,当nums[cur1] > nums[cur2]时,我们就一次性找到了mid-cur1+1个符合条件的逆序对!和暴力枚举法比起来,大大提升了效率!

class Solution {
public:
    vector<int> temp;
    int reversePairs(vector<int>& record) 
    {
        temp.resize(record.size());
        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);
        ret += mergesort(nums, mid + 1, right);
        int cur1 = left, cur2 = mid + 1, i = 0;
        while(cur1 <= mid && cur2 <= right)
        {
            if(nums[cur1] <= nums[cur2])
            {
                temp[i++] = nums[cur1++];
            }
            else
            {
                ret += mid - cur1 + 1;
                temp[i++] = nums[cur2++];
            }
        }
        while(cur1 <= mid) temp[i++] = nums[cur1++];
        while(cur2 <= right) temp[i++] = nums[cur2++];
        for(int j = left; j <= right; j++)
        {
            nums[j] = temp[j - left];
        }
        return ret;
    }
};

3. 计算右侧小于当前元素的个数

315. 计算右侧小于当前元素的个数 - 力扣(LeetCode)

        不难发现,本道题目和上一道交易逆序对的总数的处理方法是非常相似的,区别在于,本题统计的是数组每个元素的右侧小于该元素的数量。则如果我们直接使用归并排序来处理,是会出现问题的,因为计算右侧小于当前元素的个数需要根据数组元素的初始下标来记录出现个数,而排序后,数组nums的顺序发生变化,我们就不能直接得到初始下标了。

        我们可以这样解决:维护一个记录nums数组初始下标的数组index,当我们对nums进行排序时,同步对index数组做相应的处理,这样一来,即便我们对nums进行排序,还是能通过index数组来找到数组元素的初始下标。

        还有一点值得一提的是,本题我们的排序应该选择降序排列而非升序排列,这是因为上一题我们求逆序对的总数实际上是通过计算左侧大于当前元素的个数来得到的,而本题要求的是右侧小于当前元素的个数,所以应该让数组降序排列。可以像上一道题一样,画图辅助理解:

        目前为止,本题所有的算法原理讲解完毕,大家可以先试着自己编写一下代码,锻炼一下自己的代码能力,这样才能够最好的提升。

class Solution {
public:
    vector<int> temp1, temp2;
    vector<int> index, ret;

    vector<int> countSmaller(vector<int>& nums) {
        int n = nums.size();
        temp1.resize(n);
        temp2.resize(n);
        index.resize(n);
        ret.resize(n, 0);

        for (int j = 0; j < n; j++)
            index[j] = j;

        mergesort(nums, 0, n - 1);
        return ret;
    }

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

        int mid = (left + right) / 2;
        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]) {
                temp1[i] = nums[cur1];
                temp2[i++] = index[cur1++];
            } else {
                ret[index[cur1]] += (right - cur2 + 1); // Update ret[index[cur1]]
                temp1[i] = nums[cur2];
                temp2[i++] = index[cur2++];
            }
        }

        while (cur1 <= mid) {
            temp1[i] = nums[cur1];
            temp2[i++] = index[cur1++];
        }

        while (cur2 <= right) {
            temp1[i] = nums[cur2];
            temp2[i++] = index[cur2++];
        }

        for (int j = left; j <= right; j++) {
            nums[j] = temp1[j - left];
            index[j] = temp2[j - left];
        }
    }
};

4. 翻转对

493. 翻转对 - 力扣(LeetCode)

        通过题目描述,大家都能发现本题和第二题也是挺相似的,不过本题要求的是满足i<j且nums[i]>2*nums[j]的(i,j)个数,这个比较条件并不像第二题一样和归并排序的排序过程完美重合,但是通过和第二题相似的思路,我们利用归并排序处理过的两个数组来求翻转对还是可以大大优化效率,所以求翻转对的操作应该是要放在左排序、右排序之后,在合并两个数组之前的。

        另外,int类型是有大小范围限制的,本题给的数据比较大,会出现溢出的情况,所以我们的判断不直接使用nums[i]>2*nums[j],而是使用nums[i]/2.0>nums[j]。

        

class Solution {
public:
    vector<int> temp;
    int reversePairs(vector<int>& nums) 
    {
        temp.resize(nums.size());
        return mergesort(nums, 0, nums.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);
        ret += mergesort(nums, mid + 1, right);
        int cur1 = left, cur2 = mid + 1, i = 0;
        while(cur1 <= mid && cur2 <= right)
        {
            if(nums[cur1] / 2.0 <= nums[cur2]) cur2++;
            else
            {
                ret += right - cur2 + 1;
                cur1++;
            }
        }
        cur1 = left, cur2 = mid + 1;
        while(cur1 <= mid && cur2 <= right)
        {
            temp[i++] = nums[cur1] <= nums[cur2] ? nums[cur2++] : nums[cur1++]; 
        }
        while(cur1 <= mid) temp[i++] = nums[cur1++];
        while(cur2 <= right) temp[i++] = nums[cur2++];
        for(int j = left; j <= right; j++)
            nums[j] = temp[j - left];
        return ret;
    }
};

总结

        本篇文章从归并排序开始,带着大家使用分治算法解决了几道算法题,通过这几道题目的练习,我们学习到了包括但不限于:

1. 分治算法的思想:将问题分解为小的子问题,递归解决子问题,然后将结果合并来解决原始问题。

2. 归并排序的实现:数组的划分、递归排序和合并操作。

3. 归并排序算法的稳定性,即排序过程中相等元素的相对位置不会发生变化。

4. 通过分治和排序对暴力枚举算法进行优化。

        大家可以收藏本文,以后再碰到分治算法题可以再回过头看看这篇文章,相信会有不一样的理解。

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

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

相关文章

什么是蓝牙芯片?蓝牙芯片和蓝牙模块的区别

蓝牙芯片&#xff0c;是一种集成了蓝牙无线通信技术的微型电子元件。它如同一个微小的通信枢纽&#xff0c;能够在各种电子设备之间建立无线连接&#xff0c;实现数据的传输与共享。蓝牙芯片的设计精妙而复杂&#xff0c;内部集成了射频前端、数字基带、协议栈等多个功能模块&a…

Linux中nohup(no hang up)不挂起,用于在系统后台不挂断地运行命令,即使退出终端也不会影响程序的运行。

nohup的英文全称是 no hang up&#xff0c;即“不挂起”。这个命令在Linux或Unix系统中非常有用&#xff0c;主要用于在系统后台不挂断地运行命令&#xff0c;即使退出终端也不会影响程序的运行。默认情况下&#xff08;非重定向时&#xff09;&#xff0c;nohup会将输出写入一…

linux中常见的协议、服务端口整理汇总

&#x1f341;博主简介&#xff1a; &#x1f3c5;云计算领域优质创作者 &#x1f3c5;2022年CSDN新星计划python赛道第一名 &#x1f3c5;2022年CSDN原力计划优质作者 ​ &#x1f3c5;阿里云ACE认证高级工程师 ​ &#x1f3c5;阿里云开发者社区专家博主 &#x1f48a;交流社…

排序系列 之 插入排序

&#xff01;&#xff01;&#xff01;排序仅针对于数组哦本次排序是按照升序来的哦 介绍 插入排序英文名为InsertSort 基本思路 1、认为数组当中的第一个数值已经排好序了2、定义一个游标从第二个数值开始不断地向后进行遍历3、游标指向的数据插入已经排好序的数组中 代码…

力扣刷题之2959.关闭分部的可行集合数目

题干描述 一个公司在全国有 n 个分部&#xff0c;它们之间有的有道路连接。一开始&#xff0c;所有分部通过这些道路两两之间互相可以到达。 公司意识到在分部之间旅行花费了太多时间&#xff0c;所以它们决定关闭一些分部&#xff08;也可能不关闭任何分部&#xff09;&…

AV1 编码标准屏幕内容编码技术概述

AV1 屏幕内容编码 为了提高屏幕捕获内容的压缩性能&#xff0c;AV1采用了几种编码工具&#xff0c;例如用于处理屏幕画面中重复模式的内帧内块复制&#xff08;IntraBC&#xff09;&#xff0c;以及用于处理颜色数量有限的屏幕块的调色板模式。 帧内块拷贝 AV1 编码中的 Intra …

【Vue】快速入门:构建你的第一个Vue 3应用

文章目录 一、Vue简介二、环境搭建1. 安装Node.js和npm2. 安装Vue CLI 三、创建Vue项目四、项目结构介绍五、组件基础创建一个组件使用组件 六、模板语法插值指令v-bindv-ifv-for 七、事件处理八、状态管理安装Vuex创建Store使用Store 九、路由基础安装Vue Router配置路由使用路…

FFmpeg播放视频

VS2017+FFmpeg6.2.r113110+SDL2.30.5 1.下载 ShiftMediaProject/FFmpeg 2.下载SDL2 3.新建VC++控制台应用 3.配置include和lib 4.把FFmpeg和SDL的dll 复制到工程Debug目录下,并设置调试命令

24年Hvv准备,6大方向,33篇技战法

进去不少小伙伴后台留言说需要技战法&#xff0c;因此小编对市面上的技战法进行了收集和总结&#xff0c;并对收集来的技战法进行了分类&#xff0c;总共分了6大类&#xff0c;共计33篇&#xff1a; 有需要的小伙伴关注我&#xff0c;点击在看&#xff0c;并私信回复“技战法”…

IO、进程、线程03

第一题&#xff1a;预习 opendir 和 readdir函数 opendir 和 readdir 是两个在C语言&#xff08;特别是使用POSIX标准的系统&#xff0c;如Linux和UNIX&#xff09;中用于目录遍历的函数。这两个函数属于标准的C库中的目录操作部分&#xff0c;通常与<dirent.h>头文件一…

MySQL学习记录 —— 이십일 MySQL服务器文件系统(1)

文章目录 1、配置和默认值2、系统变量和选项1、介绍2、常用选项3、如何使用系统变量 3、常用服务器配置4、查看状态变量5、MySQL数据目录 mysql的服务端就是mysqld&#xff0c;d就是daemon&#xff0c;守护进程的意思。 配置文件中[mysqld]部分时服务器支持的启动选项。服务器…

某航空制造业集团IT信息化总体规划方案

获取完整方案见下图 更多有关华为研发管理/IPD、MBSE、PLM、ERP、MES、数据治理、数字样机等方面免费解决方案、资料获取&#xff0c;请见下图

开放式耳机哪个品牌好?开放式耳机实用推荐

开放式耳机是一种耳机类型&#xff0c;其外壳是开放的&#xff0c;发声单元的背面和外界相通。这种耳机的外壳上通常有许多小孔或者直接能够看到内部的发声单元。 而且开放式耳机几乎摒弃了传统耳机几乎所有缺点&#xff01;&#xff01; ❌有线耳机&#xff1a;运动的时候特别…

如何走出低能量状态?

晚上好。 每个人都难免会有状态不佳的时候。可能是遭受压力&#xff0c;可能是事情不顺&#xff0c;也可能无缘无故、突然就陷入情绪的低谷之中。 这时&#xff0c;我们很容易感到精力不济&#xff0c;无精打采&#xff0c;明明有许多事情要做和想做&#xff0c;但总是提不起精…

宠物空气净化器哪款品牌好?口碑好的猫用空气净化器排名

猫咪每年掉毛两次&#xff0c;一次掉半年的现象真让人头疼。作为一位5年资深铲屎官&#xff0c;特别是在掉毛季节&#xff0c;猫毛无处不在&#xff0c;对此深有体会。宠物空气净化器已成为铲屎官们的救星&#xff0c;能迅速清理家中的宠物毛发和异味&#xff0c;是养猫家庭的必…

RK3568平台(环境篇)windon与ubuntu之间文件互传

一.windon与ubuntu共享文件夹 打开设置&#xff1a; 点击选项&#xff0c;共享文件夹 共享文件夹&#xff0c;就是在电脑的固定盘符下面&#xff0c;找一个文件夹为Windows和Linux都能看得见的共用的看得见的文件夹&#xff0c;点击添加文件夹。 点击确定后在ubuntu添加共享文…

python 获取Shopee虾皮商家店铺商品列表 虾皮api数据采集

此api接口可用于获取虾皮平台商家店铺的商品列表&#xff0c;目前land参数支持id、vn、my、th、sg、ph、tw&#xff08;印尼、越南、马来、泰国、新加坡、菲律宾、台湾&#xff09;。 若有需要&#xff0c;请点击文末链接联系我们。 详细采集页面如下https://shopee.tw/yuesh…

【快速逆向一/无过程/有源码】《大学》在线投稿系统

逆向日期&#xff1a;2024.07.18 使用工具&#xff1a;Node.js 加密工具&#xff1a;Crypto-js标准库 文章全程已做去敏处理&#xff01;&#xff01;&#xff01; 【需要做的可联系我】 【点赞 收藏 关注 】仅供学习&#xff0c;仅供学习&#xff0c; 本文为快速逆向&#x…

IC测试:Shmooing, Shmoo测试, Shmoo图

Shmoo测试/Shmooing 在半导体测试中&#xff0c;Shmooing是一种测试技术&#xff0c;通过扫描一个范围内的测试条件参数来查看正在运行的被测器件&#xff0c;就像它在现实世界中的表现一样。 测试的参数类型取决于IC的目的和类型以及环境。至少绘制了两个参数。&#xff08;…

鸿蒙仓颉语言【基础-数据类型dataType】

数据类型 仓颉编程&#xff08;CangjieLang&#xff09;是强类型语言,变量和函数参数都需要显式声明类型&#xff0c;例如&#xff1a; public let growable: Bool false public func get(value: Array<Byte>): Int64。基础数据类型 仓颉&#xff08;CangjieLang&…