C++算法 —— 分治(2)归并

news2024/12/23 8:52:40

文章目录

  • 1、排序数组
  • 2、数组中的逆序对
  • 3、计算右侧小于当前元素的个数
  • 4、翻转对


1、排序数组

排序数组

在这里插入图片描述

排序数组也可以用归并排序来做。

    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)
    {
        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)
        {
            tmp[i++] = nums[cur1] <= nums[cur2] ? nums[cur1++] : nums[cur2++];
        }
        while(cur1 <= mid) tmp[i++] = nums[cur1++];
        while(cur2 <= right) tmp[i++] = nums[cur2++];
        for(int i = left; i <= right; i++)
        {
            nums[i] = tmp[i - left];
        }
    }

2、数组中的逆序对

剑指 Offer 51. 数组中的逆序对

在这里插入图片描述

如果暴力枚举,一定是可以解决问题的,但肯定不用这个解法。选择逆序对,可以先把数组分成两部分,左半部分 + 右半部分的逆序对,以及再找左半部分的数字和右半部分数字成对的数,比如上面例子中,7和6,7和4就是这种情况。左 + 右 + 一左一右就是整体的逆序对数量。当这两半部分都处理完后,就扩大区间,继续上述操作。这个解法也就是利用归并排序,归并排序的思路就是划分到最小的区间,只有1个数,它一定是有序的,回到上一层,也就是2个数的区间,让它们排好序,在它右边的也是2个数的区间,重复和它一样的操作,这样两个区间都有序后,再往上走一层,来到4个数的区间,4个数,每一半都是有序的,将整体的4个数排成有序的,再往上走,来到8个数的区间,重复操作。

利用归并排序的思路,我们在两个区间都排成升序后,定义两个指针cur指向两个区间的开头,然后一左一右比较大小,如果cur1比cur2大,那么cur1之后都比cur2大,就可以一次性加上多个逆序对的个数。

下面的代码可以从最小的区间开始一个个代入来理解。从只有2个数的区间开始,走到递归处,分成2个只有1个数的区间,那就会返回0,两处递归走完,来到下面的循环,此时left是0,right是1,mid是0,带入进去会发现,最后的ret只会是0或者1,并且这2个数也在最后排好序了,返回后,来到上一层,也就是走左边递归的那行代码,然后再走右边,也是2个数,也是同样的操作,2个区间排好序了,4个数的区间就一左一右比较大小,找出逆序对,排好序,再走到上一层,8个数的区间也是如此。

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

    int mergeSort(vector<int>& nums, int left, int right)
    {
        if(left >= right) return 0;
        int ret = 0;
        //1. 找中间点,将数组分成两部分
        int mid = (left + right) >> 1;
        // [left, mid] [mid + 1, right]
        //2. 左边个数 + 排序 + 右边个数 + 排序
        ret += mergeSort(nums, left, mid);
        ret += mergeSort(nums, mid + 1, right);
        //3. 一左一右的个数
        int cur1 = left, cur2 = mid + 1, i = 0;
        while(cur1 <= mid && cur2 <= right)//while体内原本是归并排序的代码,现在就多加一点
        {
            if(nums[cur1] <= nums[cur2]) tmp[i++] = nums[cur1++];
            else
            {
                ret += mid - cur1 + 1;
                tmp[i++] = nums[cur2++];
            }
        }
        //4. 处理排序
        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;
    }
};

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

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

在这里插入图片描述

此题和上一个题有相同之处,也是分治,也是利用归并排序,这道题可以看作,当前元素后面,有多少比我小的,而上一题则是当前元素前面,有多少比我大的。仔细想一想,上一题是排升序,这一题排降序则更为合适。这题和上一题还有不同的地方。

cur1和cur2,排成降序,如果cur1 <= cur2,cur2++,因为我们要找比当前元素小的;如果cur1 > cur2,由于是降序,那么cur2之后的肯定都小,但这里不能加上ret,我们要返回一个数组,要把这个数加在当前元素的原始下标,因为数组已经被我们给排序了,所以要找原始下标。这里的做法就是从最一开始就除了tmp外,再定义一个数组,存储着原始下标,因为这时候还没开始排序,可以找得到,然后每次原数组元素变换位置,这个下标数组也跟着变换。

我们要定义四个数组,一个是结果数组,一个是原始下标数组,一个是辅助数组,也就是tmp,记录改动过的顺序,一个是下标辅助数组,记录改动后的下标顺序。在while循环中,每次更新tmp,下标辅助数组也跟着更新。如果cur1大于cur2,那么除了更新,还需要往结果数组中写入个数,要在当前元素的原始下标处写入个数,这里最好要画图来理解,画原始下标和下标变动后的图。在最后for循环中的排序,除了原数组nums,还有原始下标数组也要排序。

    vector<int> index;//原始元素下标
    vector<int> res;//最终结果
    int tmp[500010];//排序辅助数组
    int tmpIndex[500010];//元素下标的辅助数组
public:
    vector<int> countSmaller(vector<int>& nums) {
        int sz = nums.size();
        index.resize(sz);
        res.resize(sz);
        for(int i = 0; i < sz; i++)
        {
            index[i] = i;
        }
        mergeSort(nums, 0, sz - 1);
        return res;
    }

    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)
        {
            if(nums[cur1] <= nums[cur2])
            {
                tmp[i] = nums[cur2];
                tmpIndex[i++] = index[cur2++];
            }
            else 
            {
                res[index[cur1]] += right - cur2 + 1;//经历了之前的排序,index已经记录下了最新的下标变动,这里就可以直接用cur1来获取正确的下标
                tmp[i] = nums[cur1];
                tmpIndex[i++] = index[cur1++];
            }
        }
        while(cur1 <= mid)
        {
            tmp[i] = nums[cur1];
            tmpIndex[i++] = index[cur1++];
        }
        while(cur2 <= right)
        {
            tmp[i] = nums[cur2];
            tmpIndex[i++] = index[cur2++];
        }
        for(int j = left; j <= right; j++)
        {
            nums[j] = tmp[j - left];
            index[j] = tmpIndex[j - left];
        }
    }

4、翻转对

翻转对

在这里插入图片描述

还是一样的思路。左半部分,右半部分,然后一左一右。不过这里的条件不一样。这里的解决办法有两个,一个是计算当前元素后面有多少元素的两倍比我小,另一个是计算当前元素之前,有多少元素的一半比我大,这两个的高效顺序分别是降序和升序。

第一个思路,cur1和cur2,找当前元素的后面,那就以cur1为重点,如果cur2的2倍大于等于cur1,cur2就往后走,如果小于,那么后面的肯定都小于。第二个思路,cur1和cur2,找当前元素的前面,那就以cur2为重点,如果cur1的一半比cur2小,那么cur1后的肯定都符合条件,如果cur1的一半大于cur2,那cur1往后走。最后合并两个有序数组。

    int tmp[50010];
public:
    int reversePairs(vector<int>& nums) {
        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 = left;//先计算翻转对,0还是left都行
        /*while(cur1 <= mid)//这里排降序,也可以排升序
        {
            while(cur2 <= right && nums[cur2] >= nums[cur1] / 2.0) cur2++;//2.0是为了防止除不尽
            if(cur2 > right) break;
            ret += right - cur2 + 1;
            cur1++;
        }*/
        while(cur2 <= right)//升序
        {
            while(cur1 <= mid && nums[cur2] >= nums[cur1] / 2.0) cur1++;
            if(cur1 > mid) break;
            ret += mid - cur1 + 1;
            cur2++;
        }
        cur1 = left, cur2 = mid + 1;//归位一下,开始排序
        while(cur1 <= mid && cur2 <= right)
        {
            tmp[i++] = nums[cur1] <= nums[cur2] ? nums[cur1++] : nums[cur2++];
            //tmp[i++] = nums[cur1] <= nums[cur2] ? nums[cur2++] : 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];
        }
        return ret;
    }

结束。

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

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

相关文章

ROS-4.创建发布者和订阅者

ros中非长连接的通信使用topic的方式&#xff0c;publisher向topic发布消息&#xff0c;subscriber订阅topic消息&#xff0c;对于非应答模式的通信适合使用该模式&#xff0c;如下图 接下来我们实现一个发布者和订阅者 1. 创建功能包 在实现订阅者和发布者的时候我们需要先…

oled--SSD1315驱动

OLED 接口方式&#xff08;由硬件电路确定&#xff09;&#xff1a;6800、8080、spi、i2c. 常见的驱动芯片&#xff1a;ssd1306、ssd1315。 oled屏幕的发光原理不同于lcd&#xff0c;上电后无法直接显示&#xff0c;需要初始化后才能正常显示。 SSD1315手册资料 SSD1315是一款…

微信小程序手机号快速验证组件调用方式

目录 一、测试环境 二、问题现象 三、总结 手机号验证组件&#xff08;包括快速验证组件和实时验证组件&#xff09;调用后无法对事件进行回调这个问题&#xff0c;先说结论&#xff0c;以下是正确的使用方式&#xff1a; <!-- 手机号快速验证组件 --> <button op…

第三章 图像到图像的映射

文章目录 第三章 图像到图像的映射3.1 单应性变换3.1.1 直接线性变换算法3.1.2 仿射变换 3.2 图像扭曲3.2.1 图像中的图像3.2.2 分段仿射扭曲3.2.3 图像配准 3.3 创建全景图3.3.1 RANSAC3.3.2 稳健的单应性矩阵估计3.3.3 拼接图像 第三章 图像到图像的映射 本章讲解…

Pandas数据分析基础—pandas自带函数map()/apply()/applymap()

文章目录 前言一、Series数据处理1、map()方法2、apply()方法3、applymap()方法总结 二、DataFrame数据处理1、map()方法2、apply()方法3、applymap()方法总结 三、map、apply、applymap三个函数区别 前言 在进行数据处理时&#xff0c;经常会对一个DataFrame展开逐行、逐列、…

three.js(二):webpack + three.js + ts

用webpackts 开发 three.js 项目 webpack 依旧是主流的模块打包工具;ts和three.js 是绝配&#xff0c;three.js本身就是用ts写的&#xff0c;ts可以为three 项目提前做好规则约束&#xff0c;使项目的开发更加顺畅。 1.创建一个目录&#xff0c;初始化 npm mkdir demo cd de…

【项目 计网8】4.23 TCP状态转换 4.24半关闭、端口复用

文章目录 4.23 TCP状态转换关于三次握手四次挥手 4.24半关闭、端口复用端口复用 4.23 TCP状态转换 2MSL(Maximum Segment Lifetime) 主动断开连接的一方&#xff0c;最后进入一个TIME_WAIT状态&#xff0c;这个状态会持续&#xff1a;2msl msl&#xff1a;官方建议&#xff1a;…

Linux 多进程解决客户端与服务器端通信

写一个服务器端用多进程处理并发&#xff0c;使两个以上的客户端可以同时连接服务器端得到响应。每当接受一个新的连接就fork产生一个子进程&#xff0c;让子进程去处理这个连接&#xff0c;父进程只用来接受连接。 与多线程相比的不同点&#xff1a;多线程如果其中一个线程操…

Kubernetes(K8s 1.28.x)部署---创建方式Docker(超详细)

目录 一、基础环境配置&#xff08;所有主机均要配置&#xff09; 1、配置IP地址和主机名、hosts解析 2、关闭防火墙、禁用SELinux 3、安装常用软件 4、配置时间同步 5、禁用Swap分区 6、修改linux的内核参数 7、配置ipvs功能 二、容器环境操作 1、定制软件源 2、安…

RocketMQ消息队列-@RocketMQMessageListener实现原理

使用Spring-RocketMQ时&#xff0c;只需要引入rocketmq-spring-boot-starter包&#xff0c;并且定义以下消费者&#xff0c;就可以很简单的实现消息消费 Component RocketMQMessageListener(topic "first-topic", consumerGroup "my-producer-group", s…

过滤器的应用-Filter

过滤器 1.工作原理 2.创建Filter 2.1通过注解的方式实现 //创建一个类&#xff0c;实现Filter接口 WebFilter(urlPatterns "/myfilter") //urlPatterns表示需要拦截的路径 public class MyFilter implements Filter {Overridepublic void doFilter(ServletReques…

深度解读NeuS代码(1):输入数据格式

准备训练数据 在preprocess_cutsom_data中&#xff0c;笔者采取第二个经过colmap的方法。但是这个方法其实需要一个前置结果&#xff0c;即colmap与LLFF的处理。接下来分别讨论&#xff1a; Colmap 一个非常好的操作流程在这里&#xff1a; https://zhuanlan.zhihu.com/p/57…

Kubernetes(k8s)安装NFS动态供给存储类并安装KubeSphere

Kubernetes安装NFS动态供给存储类并安装KubeSphere KubeSphere介绍环境准备KubeSphereNFS动态供给 安装NFS动态供给搭建NFS下载动态供给驱动修改驱动文件安装动态供给 安装KubeSphere下载KubeSphere的yaml资源清单文件安装KubeSphere 使用KubeSphere部署应用创建项目部署MySQL …

Android 13 - Media框架(9)- NuPlayer::Decoder

这一节我们将了解 NuPlayer::Decoder&#xff0c;学习如何将 MediaCodec wrap 成一个强大的 Decoder。这一节会提前讲到 MediaCodec 相关的内容&#xff0c;如果看不大懂可以先跳过此篇。原先觉得 Decoder 部分简单&#xff0c;越读越发现自己的无知&#xff0c;Android 源码真…

nginx-缓存

disk cache&#xff1a;磁盘缓存数据&#xff0c;有时间延迟&#xff0c;但是非常小&#xff0c;相对于直接请求服务器返回 对于用户来说基本无感知。 memory cache&#xff1a;磁盘缓存数据&#xff0c;基本上没有时间延迟 协商缓存&#xff08;nginx自带功能&#xff0c; 不…

机器人中的数值优化(五)——信赖域方法

本系列文章主要是我在学习《数值优化》过程中的一些笔记和相关思考&#xff0c;主要的学习资料是深蓝学院的课程《机器人中的数值优化》和高立编著的《数值最优化方法》等&#xff0c;本系列文章篇数较多&#xff0c;不定期更新&#xff0c;上半部分介绍无约束优化&#xff0c;…

18.kthread_worker:内核线程异步传输

目录 kthread_worker 驱动传输数据的方式 同步传输 异步传输 头文件 kthread_worker结构体 kthread_work结构体 kthread_flush_work结构体 init_kthread_worker()函数 为kthread_worker创建内核线程 init_kthread_work()函数 启动工作 刷新工作队列 停止内核线程…

3D点云测量:计算三个平面的交点

文章目录 0. 测试效果1. 基本内容文章目录:3D视觉测量目录微信:dhlddxB站: Non-Stop_0. 测试效果 1. 基本内容 计算三个平面的交点需要找到满足所有三个平面方程的点。三个平面通常由它们的法向量和通过它们的点(或参数形式的方程)来定义。以下是计算三个平面的交点的一般步…

在VScode中使用sftp传输本地文件到服务器端

安装SFTP 在VScode的扩展中安装sftp 注意这里需要在你没连接服务器的状态下安装&#xff0c;即本机需要有sftp 配置传输端口 安装成功后&#xff0c;使用快捷键"ctrlshiftp",输入sftp&#xff0c;选择Config 根据自己的实际情况修改配置文件&#xff0c;主要改h…

设计模式-6--装饰者模式(Decorator Pattern)

一、什么是装饰者模式&#xff08;Decorator Pattern&#xff09; 装饰者模式&#xff08;Decorator Pattern&#xff09;是一种结构型设计模式&#xff0c;它允许你在不修改现有对象的情况下&#xff0c;动态地将新功能附加到对象上。这种模式通过创建一个包装类&#xff0c;…