当前位置: 首页 > article >正文

初识算法 · 分治(3)

目录

前言:

归并排序

题目解析

算法原理

算法编写

求逆序对总数

题目解析

算法原理

算法编写


前言:

​本文的主题是分治,通过两道题目讲解,一道是归并排序,一道是求逆序对。
链接分别为:

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

 LCR 170. 交易逆序对的总数 - 力扣(LeetCode)
题目分为三个部分讲解,一是题目解析,二是算法原理,三是算法编写,那么,话不多说,直接进行主题咯。


归并排序

题目解析

其实这个题目我们已经在分治1里面做过了,但是在分治1里面使用的是快排,本文介绍分治的另一种算法,即归并排序。

直接就进入原理吧!

算法原理

对于归并排序来说,基本思想是将数组不断的划分,不断的划分,直到划分到了一个数的情况,这么做的原因是为了后面方便合并数组,你想,如果存在两个有序数组,我们想要合并这个有序数组是不是十分容易?

一个双指针就可以搞定了。

那么对于归并算法同理,我们将数组不断的划分,不断的划分,直到划分为一个元素,此时,我们将该元素视为有序的,所以分治的第一步就完成了,我们应该递归回去了。回去之后,只需要完成一个操作就可以了,也就是合并有序数组。

那么对于归并排序来说,是将左右划分,并排好序,最后合并,这其实就是树的后序遍历:

对于快排来说,是先确定好了一个元素的位置,然后排序左右两边,这实际上是一种前序遍历:

现在直接算法编写吧!

算法编写

class Solution 
{
public:
    vector<int> tmp;
    void MergeSort(vector<int>& nums, int left, int right)
    {
        if(left >= right) return;
        //划分中间值
        int mid = (right + left) >> 1;
        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];
    }
    vector<int> sortArray(vector<int>& nums) 
    {
        tmp.resize(nums.size());
        MergeSort(nums, 0, nums.size() - 1);
        return nums;
    }
};

求逆序对总数

题目解析

在大一或者是大二的时候,多多少少都是学习过逆序对的,逆序对的概念就是前面的数字大于后面的数组,那么这两个数组成的数对,就成为逆序对。

那么题目是给我们一个数组,让我们求该数组里面的逆序对的个数。

算法原理

对于该题目,我们可以直接脑袋一空,直接就思考如何进行暴力解法,那多简单,是吧!

我们直接两个for循环,第一个For循环用来固定一个数,遍历其他数,判断是否满足逆序对的条件即可。该暴力解法的时间复杂度是O(N^2),在这道题目肯定是跑不过的,要不然也太对不起hard标签了。

所以,对于这道题目我们可以使用归并的思想,可能部分同学会觉得归并的思想和这道题并不搭边,我们不妨简单思考一下:

我们要求逆序对,那么该数组划分为两个区间之后,左边的逆序对 + 右边的逆序对,最后从左边选择数,再从右边选择数计算剩余的逆序对,三个逆序对的数一相加,我们就可以得到了总的逆序对个数。

但是但是,如果我们直接就是左边遍历右边遍历,那和第一种暴力解法也没有好到哪里去,所以从左边找和从右边找的时候,我们如果带上排序试试?

比如我们将左右两个数的逆序对找到了之后,顺便将这两个区间排序,那么,如果我们从左边找到一个数,从右边找一个数,如果左边的数字大于右边的数字,那么左区间的后面的数是不是都大于了后面的数字了?

这就爽了,我们直接就是+= mid - cur1 + 1就可以了。

那么排序我们排升序还是降序呢?

如果我们排序的是降序,遍历的过程,是极大有可能出现重复的:

此时这个策略就不可以了,现在的策略是找到有多少个数比他大。但是如果我们换一个策略呢?

找有多少个数比他小呢?

此时降序就十分吃香了。

所以降序和升序实际上都是可以的。

算法编写

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 = 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;
    }

感谢阅读!​


http://www.kler.cn/a/408921.html

相关文章:

  • 【大数据学习 | Spark-Core】Spark提交及运行流程
  • uni-app 界面TabBar中间大图标设置的两种方法
  • Docker安装ubuntu1604
  • D73【 python 接口自动化学习】- python 基础之正则表达式
  • IDEA怎么定位java类所用maven依赖版本及引用位置
  • 取电快充协议芯片,支持全协议、内部集成LDO支持从UART串口读取电压电流消息
  • Excel求和如何过滤错误值
  • 设计模式——数据访问对象模式
  • Spring Boot与MyBatis-Plus的高效集成
  • 不需要双手离开键盘 vscode
  • 复古风格渐变褪色人像旅拍Lr调色教程,手机滤镜PS+Lightroom预设下载!
  • 电脑的ip地址怎么换掉:全面指南
  • [Java网络安全系列面试题] GET 和 POST 的区别在哪里?
  • SHELL笔记(循环)
  • 神经网络的初始化
  • SQL 语句访问路径的方式
  • 【数据结构与算法】 LeetCode:回溯
  • 解锁PPTist的全新体验:Windows系统环境下本地部署与远程访问
  • [C/C++][FFmpeg] 增加引用计数和显式释放的接口
  • RHCE——DNS域名解析服务器
  • 深度学习中的经典模型:卷积神经网络(CNN)基础与实现
  • 什么是C++的移动语义,它的作用什么?
  • NVR管理平台EasyNVR多个NVR同时管理:全方位安防监控视频融合云平台方案
  • DataWhale—PumpkinBook(TASK05决策树)
  • 前端VUE项目启动方式
  • AI模型---安装cuda与cuDNN