【优选算法】---分治 归并排序

news2024/11/25 19:21:27

分治 归并排序

  • 一、排序数组 / 归并排序的复习
    • 1、题目解析
    • 2、算法原理
    • 3、代码
  • 二、逆序对的总数
    • 1、题目解析
    • 2、算法原理
    • 3、代码
  • 三、计算右侧小于当前元素的个数
    • 1、题目解析
    • 2、算法原理
    • 3、代码
  • 四、翻转对
    • 1、题目解析
    • 2、算法原理
    • 3、代码

一、排序数组 / 归并排序的复习

归并排序的主要步骤:

①:就是找一个数组里面的中间数mid,将这个数组分为两部分,左半部分和右半部分。然后再从左半部分和右半部分里面再找一个中间数mid,依次不断的递归下去,直到分到最小单位为止,排完序之后从下往上返回,这就是归并排序的递归。
②:合并两个有序数组(双指针法)
分别对左半部分数组和右半部分数组,定义一个下标移动的变量cur1和cur2,然后进行循环比较。

1、题目解析

在这里插入图片描述

题目链接:排序数组

2、算法原理

分治的思想:归并排序类似于二叉树的后序遍历
在这里插入图片描述
主要步骤:

1. 中间点的划分
2. 对左右区间的递归
3. 合并两个有序数组
4. 还原

3、代码

class Solution 
{
    vector<int> tmp;
    // (2)第二种方法,在全局开一个临时数组tmp(用来保存每次递归合并的两个有序数组),
    //  然后就不用每次递归还创建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;

        // 1、中间点划分区间
        int mid=(left+right)>>1;
        // [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++];


        // 4、还原
        for(int i=left;i<=right;i++)
        {
            nums[i]=tmp[i-left];
        }
    }

};

二、逆序对的总数

1、题目解析

在这里插入图片描述
数组中的逆序对 链接

2、算法原理

(1)将整个数组分为左半部分和右半部分,
(2)分别在左半部分找逆序对的个数,排完序。
(3)然后再在右半部分找逆序对的个数再排排序,
(4)最后再一左一右利用双指针的算法,固定一个然后移动另外一个在:一左一右->这两个数组里面分别找逆序对的个数,最后加起来就是整个数组的逆序对个数。

在这里插入图片描述
这样的解法的时间复杂度:N*logN

在这里插入图片描述
关键的细节:
在这里插入图片描述

3、代码

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;
        
        // 1、找中间值mid划分为左右两部分
        int mid=(left+right)>>1;
        // [left,mid]  [mid+1,right]

        int ret=0;// 记录逆序对个数
        // 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)
        {
            if(nums[cur1]<=nums[cur2]) 
            {
                tmp[i++]=nums[cur1++];
            }
            else
            {
                //ret+=cur2-(mid+1)+1;  这种情况不行,因为我们是以左边为基准的,只要cur1遍历完,一左一右这个过程就结束了!

                ret+=mid-cur1+1;// 因为是升序,所以在左半部分,只要此时的cur1>cur2,那么位于cur1右侧~mid之间的数据全部>cur2
                tmp[i++]=nums[cur2++];
            }
        }

        // 3、处理没遍历完的
        while(cur1<=mid) tmp[i++]=nums[cur1++];
        while(cur2<=right) tmp[i++]=nums[cur2++];

        // 4、还原
        for(int j=left;j<=right;j++)
        {
            nums[j]=tmp[j-left];// 始终不太理解 还原的时候,为啥要用j-left???不能直接nums[j]=tmp[j]吗???
        }
        return ret;
    }
};

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

1、题目解析

在这里插入图片描述
链接:计算右侧小于当前元素的个数

2、算法原理

这道题仍然是利用归并排序的分治思想
在这里插入图片描述

但是这次我们要排的是:降序!
在这里插入图片描述
但是有一点需要特别注意,这是一个重点也是一个难点。

题目中需要返回的是一个数组!
一个什么样的数组呢?原始数组下标所对应位置,有多少个右面比我小的元素。

这里的重点和难点就在于:我们如何找到我们正在遍历的数组(是打乱排完序的)中当前元素的原始下标!

解决办法就是:在进行分治归并排序之前,对原始数组利用哈希思想进行下标绑定。

在这里插入图片描述
就是你进行递归排序的时候,你的下标跟着你的数字是一起移动的!!!

3、代码

class Solution 
{
    vector<int> ret;// 返回答案的数组
    vector<int> index;// 创建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 ;

        // 1、找中间数mid
        int mid=(left+right)>>1;
        //[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)
        {
            // 降序排序
            if(nums[cur1]<=nums[cur2])
            {
                tmpNums[i]=nums[cur2];
                tmpIndex[i++]=index[cur2++];
            }
            else
            {
                //tmpNums[index[i]]+=right-cur2+1;

                // 重点!(统计此时cur1位置有多少符合条件的,因为cur1原本是在nums中遍历的,但是由于,index跟nums是一一对应的关系,所以index[cur1]就代表着原始数据的下标)
                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++];
        }

        // 4、还原
        for(int j=left;j<=right;j++)
        {
            nums[j]=tmpNums[j-left];
            index[j]=tmpIndex[j-left];
        }

    }
};

四、翻转对

1、题目解析

在这里插入图片描述
链接:翻转对

2、算法原理

主要思想还是用归并排序的分治思想

在这里插入图片描述
1、运用递归分别:处理左半部分、右半部分
2、运用“ 单调性+同向的双指针 ” 处理一左一右的情况

在这里插入图片描述

3、代码

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、中间数mid
        int mid=(left+right)>>1;

        // 2、处理左半部分、右半部分
        ret+=mergeSort(nums,left,mid);
        ret+=mergeSort(nums,mid+1,right);
        
        // 3、一左一右(固定cur1不动,cur2移动)
        int cur1=left,cur2=mid+1,i=left;
        while(cur1<=mid)
        {
            while(cur2<=right&&nums[cur1]/2.0<=nums[cur2])// 因为 *2会溢出,所以我们用/2.0
            {
                cur2++;
            }
            // 接下来就是符合条件的
            ret+=right-cur2+1;
            cur1++;
        }

        // 4、合并两个有序数组
        cur1=left,cur2=mid+1;
        
        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++];

        // 4、还原
        for(int j=left;j<=right;j++)
        {
            nums[j]=tmp[j];
        }
        return ret;
    }
};

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

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

相关文章

Windows下Jenkins控制台中文乱码

问题描述 问题情况如下图&#xff1a; 环境信息 Windows 11 家庭中文版java 21.0.4 2024-07-16 LTSJenkins 2.452.3 解决方法 增加系统JAVA_TOOL_OPTIONS&#xff0c;并设置值为-Dfile.encodingGBK。 打开设置方法&#xff1a;桌面上右键点击“此电脑”图标&#xff0c;选…

软考高级之系统架构师之计算机硬件基础

概述 局部性原理是指在指定时间内&#xff0c;程序趋于在有限的内存区域内重复访问。通常将局部性分为空间局部性和时间局部性。空间局部性是指已访问过的内存地址附近的位置很可能被连续访问。时间局部性是指已访问过的内存地址在较短的时间内还可能被多次访问。 计算机执行…

请散户股民看过来,密切关注两件大事

明天股市要开市&#xff0c;不仅散户股民期盼节后股市大涨&#xff0c;上面也同样想在节后来上一个“开门红”。 为此&#xff0c;上面没休假&#xff0c;关起门来办了两件大事&#xff0c;这两天发布消息已提前预热了。 两件大事如下&#xff1a; 一是&#xff0c;上交所10…

柯桥生活口语学习之在化妆品店可以用到的韩语句子

화장품을 사고 싶어요. 我想买化妆品。 어떤 화장품을 원하세요? 您想买什么化妆品。 스킨로션을 찾고 있어요. 我想买化妆水&#xff0c;乳液。 피부 타입은 어떠세요? 您是什么皮肤类型&#xff1f; 민감성 피부예요. 我是敏感性皮肤。 평소에 쓰시는 제품은 뭐예…

【判断推理】逻辑基础

1.1 命题 用语言、符号或者式子表达的&#xff0c;可以判断真假的陈述句称为命题&#xff0c;一般写为 若p&#xff0c;则q 真命题&#xff1a;判断为真的语句假命题&#xff1a;判断为假的语句 eg1&#xff1a;小张是中国人&#xff08;若是小张&#xff0c;则是中国人&#…

【传感器技术】【9 温度测量,热电偶传感器,膨胀式温度传感器,压力测量,弹性式压力表】

上理考研周导师的哔哩哔哩频道 我在频道里讲课哦 目录 9.1、 温度概述 1&#xff0e; 温度与温标 2&#xff0e; 温度测量的主要方法和分类 9.2、 膨胀式温度传感器 1&#xff0e; 液体膨胀式 2&#xff0e; 固体膨胀式 3&#xff0e; 气体膨胀式 9.3、 热电偶传感器 1. …

Cesium的一些神奇概念及技术流程(1)

近期要深度研究Cesium。关于Cesium的用法、渲染流程等方面我看很多人都写过。我就写写其中一些可能平时用不到但是比较有趣的内容。因为边研究边写&#xff0c;所以会陆续出几集&#xff0c;然后合并在一起&#xff0c;欢迎大家跟踪。 我的这些文章不打算把一些基本概念展开解…

OJ在线评测系统 微服务 OpenFeign调整后端上 内部调用Client代码书写 实现客户端接口 配置服务参数 接口文档

OpenFeign内部调用上 我们的代码已经搬运完毕了 但是我们的服务之间是无法相互调用的 我们可以使用OpenFeign进行远程调用 一个http调用客户端 提供了更方便的方式让你远程调用其他的服务 Nacos注册中心获取服务的调用地址 如果没有实现OpenFeign 也能实现跨服务的调用 …

Windows 11 version 24H2 LTSC 2024 中文版、英文版 (x64、ARM64) 下载 (updated Oct 2024)

Windows 11 version 24H2 & LTSC 2024 中文版、英文版 (x64、ARM64) 下载 (updated Oct 2024) Windows 11, version 24H2&#xff0c;企业版 arm64 x64 请访问原文链接&#xff1a;https://sysin.org/blog/windows-11/ 查看最新版。原创作品&#xff0c;转载请保留出处。…

出国必备神器!这5款中英翻译工具让你秒变外语达人

在这个全球化的时代&#xff0c;中英互译已然成为我们日常生活和工作中不可或缺的一环。面对众多的翻译工具&#xff0c;如何选择一款既高效又人性化的翻译助手呢&#xff1f;今天&#xff0c;就让我为大家揭秘几款热门的中英互译工具&#xff0c;并分享我的使用感受。 一、福昕…

【数据结构与算法】线性表

文章目录 一.什么是线性表&#xff1f;二.线性表如何存储&#xff1f;三.线性表的类型 我们知道从应用中抽象出共性的逻辑结构和基本操作就是抽象数据类型&#xff0c;然后实现其存储结构和基本操作。下面我们依然按这个思路来认识线性表 一.什么是线性表&#xff1f; 定义 线性…

【项目记录】大模型基于llama.cpp在Qemu-riscv64向量扩展指令下的部署

概述 本文在qemu-riscv64平台上&#xff0c;利用向量扩展指令加速运行基于llama.cpp构建的大模型。 参考博客链接&#xff1a; Accelerating llama.cpp with RISC-V Vector Extension 基于RVV的llama.cpp在Banana Pi F3 RISCV开发板上的演示 llama.cpp工程 Llama.cpp是一个基…

C语言-指针变量,常量与数组名的细微区别辨析

本节根据两个选择题进行展开辨析 一、例1 本题答案&#xff1a;C 解析&#xff1a;强干扰选项是B&#xff0c;我相信大多数同学都会在B&#xff0c;C之间犹豫好久&#xff0c;那么为什么答案会最终选择C呢&#xff1f;因为本题在定义函数&#xff0c;所以a首先是一个数组名&a…

【学习笔记】一种使用多项式快速计算 sin 和 cos 近似值的方法

一种使用多项式快速计算 sin 和 cos 近似值的方法 在嵌入式开发、游戏开发或其他需要快速数学计算的领域&#xff0c;sin 和 cos 函数的计算时间可能会影响程序的整体性能。特别是在对时间敏感、精度要求不高的场景中&#xff0c;传统的 sin 和 cos 函数由于依赖复杂的数值方法…

SOMEIP_ETS_168: SD_TestFieldUINT8Reliable

测试目的&#xff1a; 验证DUT能够通过Getter和Setter方法正确地发送和接收TestFieldUINT8Reliable字段的值&#xff0c;并且这些操作是可靠的。 描述 本测试用例旨在确保DUT的ETS能够响应Tester的请求&#xff0c;正确地使用Getter方法获取TestFieldUINT8Reliable的值&…

【MySQL必知会】事务

目录 &#x1f308;前言&#x1f308; &#x1f4c1; 事务概念 &#x1f4c1; 事务操作 &#x1f4c1; 事务提交方式 &#x1f4c1; 隔离级别 &#x1f4c1; MVCC &#x1f4c2; 3个隐藏列字段 &#x1f4c2; undo日志 &#x1f4c2; Read View视图 &#x1f4c1; RR和R…

分治算法(5)_归并排序_排序数组

个人主页&#xff1a;C忠实粉丝 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 C忠实粉丝 原创 分治算法(5)_归并排序_排序数组 收录于专栏【经典算法练习】 本专栏旨在分享学习算法的一点学习笔记&#xff0c;欢迎大家在评论区交流讨论&#x1f48c; 目录 …

JavaSE——面向对象11:内部类(局部内部类、匿名内部类、成员内部类、静态内部类)

目录 一、内部类基本介绍 (一)内部类定义 (二)内部类基本语法 (三)内部类代码示例 (四)内部类的分类 二、局部内部类 三、匿名内部类(重要) (一)基本介绍 (二)基于接口的匿名内部类 (三)基于类的匿名内部类 (四)注意事项与使用细节 (五)匿名内部类的最佳实践——当…

leetcode-42. 接雨水 单调栈

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图&#xff0c;计算按此排列的柱子&#xff0c;下雨之后能接多少雨水。 示例 1&#xff1a; 输入&#xff1a;height [0,1,0,2,1,0,1,3,2,1,2,1] 输出&#xff1a;6 解释&#xff1a;上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表…