算法:分治思想处理归并递归问题

news2025/2/1 0:54:01

文章目录

  • 算法原理
  • 实现思路
  • 典型例题
    • 排序数组
    • 数组中的逆序对
    • 计算右侧小于当前元素的个数
  • 总结

算法原理

利用归并思想进行分治也是很重要的一种思路,在解决逆序对的问题上有很大的需求空间

于是首先归并排序是首先的,归并排序要能写出来:

class Solution 
{
    vector<int> tmp;
public:
    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;
        }
        // 数组划分 [left,mid][mid+1,right]
        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])
            {
                tmp[i++]=nums[cur1++];
            }
            else
            {
                tmp[i++]=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];
        }
    }
};

以上为归并排序基本算法原理,基于这个原理可以解决逆序对问题,逆序对问题通常问法是,给定某一个数据,在整个数组中找比这个数大或者比这个数小的数,统计这样的元素有多少个,进而返回到数组或者直接输出

那么在找寻这个过程中,这类问题的基本思路就是:左边找,右边找,左右找

在找寻的过程中需要注意的是升序和逆序问题,后续的题目中会有涉及到的地方,在这里不过总结

实现思路

大体的实现思路如上,总结下来就是划分为两个子区间,在左边找,在右边找,接着左右找,这样就能找到要求的结果

典型例题

排序数组

在这里插入图片描述

理解快速排序和归并排序思维的不同点

依旧是经典的排序数组问题,这次选用归并排序来解决,要了解归并排序和快速排序其实都是利用了分治的思想,把一个很复杂的问题分解为一个一个的小问题,二者在思维上有一些小小的区别,快速排序的思想是,对于某个区间来说,把这个区间进行分块,每一个分块都进行排序,每一个都进行排序,这样就完成了目的,这样的思维更像是一种前序遍历,完成了这次的任务后再向后进行延伸,而归并排序的思路和快速排序不同,归并排序的思路主要是把数组拆分成一个一个的小区间,不停的拆分,直到不能拆分后再进行组装,它的排序过程整体上而言是滞后的,更像是一种后序遍历的思想,先一直向深处找,直到找不下去了再进行排序,再一层一层向上走

class Solution 
{
    vector<int> tmp;
public:
    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;
        }
        // 数组划分 [left,mid][mid+1,right]
        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])
            {
                tmp[i++]=nums[cur1++];
            }
            else
            {
                tmp[i++]=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];
        }
    }
};

数组中的逆序对

在这里插入图片描述
利用归并排序求逆序对是解决这类问题的常见方法,对于这个题来说,就可以采用分治的方法来解决问题

具体来说,可以把整个问题拆分为几个小步骤,把当前所在区间分成两个区间,在左边的区间内找符合逆序对的对数,再在右边的区间内找符合逆序对的对数,同时把左右两区间都进行排序,这样就可以在左右区间内都寻找符合要求的逆序对数,这就是一个轮回思路,把整个数组拆分为一个一个小区间即可解决问题,这就是分治的思想

那么思路就确认了:

  1. 从左边数组中找符合要求的逆序对
  2. 从右边数组中找符合要求的逆序对
  3. 从左右两边数组中找符合要求的逆序对

从排列组合的分类原理来看,这样就能找到所有的逆序对

从优化角度来讲,第三步是可以进行优化的,这就引入了要排序的原因:

如何从左右两数组中找逆序对数?

其实利用双指针的思想就可以解决,定义cur1和cur2分别指向左右两个数组,假设这里是提前排序好的,升序的数组,那么当cur1所指向的元素大于cur2所指的元素,那么cur2所指向的元素之前的元素全部满足条件,因此一次可以找出很多相同的元素,这也是这个算法的原理

因此这里就引出了为什么要进行排序,左右子区间排序后就可以通过上面的算法快速找到有多少满足要求的逆序对

处理剩余元素

  • 如果是左边出现剩余,说明左边剩下的所有元素都是⽐右边元素⼤的,但是它们都是已经被计算过的,因此不会产⽣逆序对,仅需归并排序即可。

  • 如果是右边出现剩余,说明右边剩下的元素都是⽐左边⼤的,不符合逆序对的定义,因此也不需要处理,仅需归并排序即可。

class Solution 
{
    vector<int> tmp;
public:    

    int reversePairs(vector<int>& nums) 
    {
        tmp.resize(50001);
        return mergesort(nums,0,nums.size()-1);
    }

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

        int ret=0,mid=(left+right)/2;

        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])
            {
                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 i=left;i<=right;i++)
        {
            nums[i]=tmp[i-left];
        }

        return ret;
    }
};

总体来说还是一道有思维量的hard题目,但如果掌握了分治的思想,再去下手就会容易许多

而这样的算法的时间复杂度也是很优秀的,时间复杂度是O(N)

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

在这里插入图片描述

有了上面的题目的思维铺垫,解法还是比较好想的,原理就是利用归并排序进行分治的思想

但这个题和上面的问题也有区别,由于返回的是数组,因此需要记录nums中每一个数组中元素在返回数组中元素的下标,需要一一对应起来,这是比较关键的一步,也就是说,每次找到符合条件的数后,这个数应该被放到返回数组中的哪个位置?这就需要用一个辅助数组来记录原数组中每一个元素的下标所在的位置,这样就能找到了

class Solution 
{
    vector<int> ret;
    vector<int> index;
    int tmpnums[500010];
    int tmpindex[500010];
public:
    vector<int> countSmaller(vector<int>& nums) 
    {
        int n=nums.size();
        ret.resize(n);
        index.resize(n);
        for(int i=0;i<n;i++)
        {
            index[i]=i;
        }
        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])
            {
                tmpnums[i]=nums[cur2];
                tmpindex[i++]=index[cur2++];
            }
            else
            {
                ret[index[cur1]]+=right-cur2+1;
                tmpnums[i]=nums[cur1];
                tmpindex[i++]=index[cur1++];
            }
        }

        while(cur1<=mid)
        {
            tmpnums[i]=nums[cur1];
            tmpindex[i++]=index[cur1++];
        }
        while(cur2<=right)
        {
            tmpnums[i]=nums[cur2];
            tmpindex[i++]=index[cur2++];
        }

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

总结

归并递归解决分治问题主要依托于归并排序,在掌握归并的前提下找到归并过程中要找的关键信息

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

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

相关文章

还在苦恼如何开发一个Chrome插件吗?十分钟带你实现一个实用小插件

你是否曾考虑过创建自己的 Chrome 插件&#xff0c;但又挠头毫无思路&#xff1f;那么在接下来的几分钟里&#xff0c;我不仅会介绍 Chrome 浏览器扩展的基本知识&#xff0c;还会指导你通过五个简单的步骤来制作自己的扩展。 知道怎么做吗&#xff1f;让我们一探究竟&#xff…

探索在云原生环境中构建的大数据驱动的智能应用程序的成功案例,并分析它们的关键要素。

文章目录 1. Netflix - 个性化推荐引擎2. Uber - 实时数据分析和决策支持3. Airbnb - 价格预测和优化5. Google - 自然语言处理和搜索优化 &#x1f388;个人主页&#xff1a;程序员 小侯 &#x1f390;CSDN新晋作者 &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏 ✨收录专…

EG1164大功率同步整流升压模块开源,最高效率97%

EG1164大功率同步整流Boost升压电源模块&#xff0c;最高效率97%&#xff0c;输入电压8~50V&#xff0c;输出电压8~60V可调&#xff0c;最大功率300瓦以上&#xff0c;开关频率219kHz。 白嫖了张嘉立创的彩色丝印券就随便画了个板试试&#xff0c;第一次打彩色丝印。 因为我测…

word导出为HTML格式教程,同时也导出图片

在写文档教程时&#xff0c;有时需要借鉴人家的专业文档内容&#xff0c;一般都是word格式文档。word直接复制里面的内容&#xff0c;帐帖到网站编辑器会有很多问题&#xff0c;需要二次清楚下格式才行&#xff0c;而且图片是没办法直接复制到编辑器内的。所以最方便的办法是将…

ARM编程模型-指令流水线

流水线技术通过多个功能部件并行工作来缩短程序执行时间&#xff0c;提高处理器核的效率和吞吐率&#xff0c;从而成为微处理器设计中最为重要的技术之一。 1. 3级流水线 到ARM7为止的ARM处理器使用简单的3级流水线&#xff0c;它包括下列流水线级。 &#xff08;1&#xff0…

如何中mac上安装多版本python并配置PATH

摘要 mac 默认安装的python是 python3&#xff0c;但是如果我们需要其他python版本时&#xff0c;该怎么办呢&#xff1f; 例如&#xff1a;需要python2 版本&#xff0c;如果使用homebrew安装会提示没有python2。同时使用python --version 会发现commond not found。 所以本…

linux安装minio以及springboot整合使用

文章目录 1.linux安装minio2.springboot整合minio使用 1.linux安装minio 1.新建文件夹 mkdir /home/minio # 数据文件夹 mkdir /home/minio/data # 创建日志文件夹 mkdir /home/minio/log2.进入文件夹 cd /home/minio3.下载minio&#xff0c;链接可从官网获取 wget https://…

有c语言的基础学习python【python基础详解】

文章将从C语言出发&#xff0c;深入介绍python的基础知识&#xff0c;也包括很多python的新增知识点详解。 目录 1.python的输入输出&#xff0c;重新认识 hello world 1.1 输出函数print的规则 1.2 输入函数input的规则 1.3 用print将数据写入文件 2.数据类型、基本操作…

SPSS统计作图教程:百分条图堆积条图

1、问题与数据 某研究者想看不同年龄分组人群&#xff08;Age_cat&#xff09;中不同程度的维生素D缺乏&#xff08;VD&#xff09;的百分构成比&#xff0c;部分数据如图1。研究者想以条图形式来展现&#xff0c;该如何操作呢&#xff1f; 图1 部分数据 2. 具体操作&#xf…

不用加减乘除做加法

不用加减乘除做加法 一、描述二、方法 一、描述 写一个函数&#xff0c;求两个整数之和&#xff0c;要求在函数体内不得使用、-、*、/四则运算符号&#xff0c;本题OJ链接 数据范围&#xff1a;两个数都满足 −10≤n≤1000 进阶&#xff1a;空间复杂度 O(1)&#xff0c;时间复…

构造函数和析构函数(个人学习笔记黑马学习)

构造函数:主要作用在于创建对象时为对象的成员属性赋值&#xff0c;构造函数由编译器自动调用&#xff0c;无须手动调用。析构函数:主要作用在于对象销毁前系统自动调用&#xff0c;执行一些清理工作。 #include <iostream> using namespace std;//对象初始化和清理class…

MVC,MVP,MVVM的理解和区别

MVC MVC &#xff0c;早期的开发架构&#xff0c;在安卓里&#xff0c;用res代表V&#xff0c;activity代表Controller层&#xff0c;Model层完成数据请求&#xff0c;更新操作&#xff0c;activity完成view的绑定&#xff0c;以及业务逻辑的编写&#xff0c;更新view&#xf…

2359. 找到离给定两个节点最近的节点;1781. 所有子字符串美丽值之和;2406. 将区间分为最少组数

2359. 找到离给定两个节点最近的节点 核心思想:统计node1和node2分别到每个点的距离&#xff0c;然后在枚举每个点统计结果。关键在于如何统计node到每个点的距离&#xff0c;首先是初始化为inf很重要&#xff0c;因为在枚举的时候&#xff0c;因为是inf代表了这个节点无法到达…

MVC模式分层练习

新建库 新建表 插入点数据 先不用MVC模式写功能,来看下缺点是什么 新建一个空项目 选项项目使用的JDK 自己的IDEA总是要重启下 新建模块 因maven还没教 添加框架支持 添加后项目多了这些 添加些必要依赖 这里注意下,如果导入jar包不对可以重新导入下或者是jar包本身出了问…

设计模式-2--工厂模式(Factory Pattern)

一、 什么是工厂模式 工厂模式&#xff08;Factory Pattern&#xff09;是一种创建型设计模式&#xff0c;它提供了一种创建对象的接口&#xff0c;但是将对象的实例化过程推迟到子类中。工厂模式允许通过调用一个共同的接口方法来创建不同类型的对象&#xff0c;而无需暴露对…

yolov5 应用整理

yolov5 应用整理 适用于0基础人员与有一定yolov5了解的人员食用. 关于yolov5参考: yolov5 github源码链接 目前参与过yolov5的应用: 平台库x86/arm cpuncnnx86libtorch/pytorchBM1684算能标准库(需要进行模型转换)昇腾cann(ascend api) https://gitee.com/Tencent/ncnn ht…

在windows下安装配置skywalking

1.下载地址 Downloads | Apache SkyWalkinghttp://skywalking.apache.org/downloads/ 2.文件目录说明 将文件解压后&#xff0c;可看到agent和bin目录&#xff1a; Agent&#xff1a;作为探针&#xff0c;安装在服务器端&#xff0c;进行数据采集和上报。 Config&#xff1a…

车联网仿真工具Veins安装指南(基于官方文档)

Veins安装指南 官方文档&#xff1a; tutorial 直接选择最easy的way&#xff1a; 下载一个虚拟机&#xff1a;VMware Workstation Player 下载即可。这个普通版是免费的&#xff0c;pro版正版是要收费的&#xff0c;可以找一些其他的方法下载pro版本&#xff0c;可以同时开…

windows11 利用vmware17 安装rocky9操作系统

下载相关软件和镜像 vmware17 下载 下载页面 Download VMware Workstation Pro ​ rocky8镜像下载 官网链接&#xff1a;Rocky Linux 下载页面 Download Rocky | Rocky Linux 点击Minimal下载 安装rocky9 选择镜像文件&#xff0c;点击下一步 点击下一步 启动虚拟机 选…

锂电池充电电路方案

锂电池充电电路一 原理图如下 都是比较小的&#xff0c;SOT-23-6 封装 此方案的优势是器件可以扩容&#xff0c;也就是可以替换成容量更大的mos管。 锂电池充电电路二 锂电池充电电路三 注意线的粗细。 引脚说明 锂电池电量检测电路 键盘上的电量检测电路原理图 电量检…