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

我爱学算法之—— 分治-归并

一、排序数组

题目解析

在这里插入图片描述

这道题就是一个简单的排序数组(与分治-快排中排序数组那道题一样)

算法思路

这里就回顾一下归并排序,使用归并排序来解决这道题。

简单来说归并排序就分为两步:

  1. 递归划分数组
  2. 合并两个有序数组

合并两个有序数组

归并排序中,划分数组是提供递归分治实现的;在整个代码逻辑中,我们只需要实现合并两个有序数组即可。

以升序排序为例

  1. 定义两个指针cur1cur2遍历两个数组(升序的);定义一个tmp数组,用来记录排序完的数组。
  2. 谁小就将谁放到tmp数组中。
  3. 遍历完之后,如果cur1/cur2(其中一个)没有遍历完,就将其后面的元素一次放入数组tmp数组中。
  4. 最后,还原数组即可。

代码实现

class Solution {
public:void msort(vector<int>& nums, int left, int right) {if (left >= right)return;// 递归划分数组int mid = left + (right - left) / 2;msort(nums, left, mid);msort(nums, mid + 1, right);// 合并两个有序数组int sz = right - left + 1;int cur1 = left, cur2 = mid + 1;vector<int> tmp(sz);int i = 0;while (cur1 <= mid && cur2 <= right) {if (nums[cur1] <= nums[cur2])tmp[i++] = nums[cur1++];elsetmp[i++] = nums[cur2++];}while (cur1 <= mid) {tmp[i++] = nums[cur1++];}while (cur2 <= right) {tmp[i++] = nums[cur2++];}// 还原数组for (i = 0; i < sz; i++) {nums[left + i] = tmp[i];}}vector<int> sortArray(vector<int>& nums) {msort(nums, 0, nums.size() - 1);return nums;}
};

二、交易逆序对的总数

题目解析

在这里插入图片描述

这道题,给定一个数组nums,要我们求出这个数组nums中的逆序对个数。(逆序对就是左边的数大于右边的数)

算法思路

解法一:暴力枚举

枚举出数组nums中的所有的二元组,判断是否是逆序对,然后统计逆序对的个数。

时间复杂度O(n^2)

解法二 :归并排序

第一次看到这道题,可能会很疑惑,这跟归并排序有什么关系呢?

归并排序是先进行数组划分,然后再合并数组中有序的两部分。

数组nums中的逆序对个数 = 左边期间逆序对个数 + 右边区间逆序对个数 + 左区间选一个数、右区间选一个数组成逆序对的个数

在这里插入图片描述

而在归并排序中,是要进行合并数组中两个有序的部分的,合并的过程不就是从leftright区间各选取一个数进行比较大小吗?

所以,在遍历过程中,leftright区间中的逆序对个数通过递归返回值过去;我们只需在合并两个有序数组的过程中,统计leftright区间各选取一个数能组成逆序对的数量

合并两个有序的数组,并统计逆序对数量这里按升序排序):

定义cur1遍历left区间、cur2遍历右区间。

  • nums[cur1] <= nums[cur2]tmp[i++] = nums[cur1++]

  • nums[cur1] > nums[cur2]ret += (mid - cur1 + 1); tmp[i++] = nums[cur2++]

    因为数组是升序排序的,nums[cur1] > nums[cur2],所以[cur1,mid]区间的所有元素都大于nums[cur2]都能和nums[cur2]组成一个逆序对。

因为这里是归并排序,在统计逆序对个数时,left区间和right区间是有序的;且left区间中任意一个元素一定在right中元素的左边。

在这里插入图片描述

这里使用升序排序,在合并两个有序数组部分时:

如果nums[cur1] > num[cur2],区间[cur1, mid]中的所有元素都大于nums[cur2];以nums[cur2]为右边值就存在mid-cur1+1个逆序对。

代码实现

class Solution {
public:int msort(vector<int>& nums, int left, int right) {if (left >= right)return 0;int mid = left + (right - left) / 2;int ln = msort(nums, left, mid);int rn = msort(nums, mid + 1, right);// 合并两个有序数组、并统计逆序对数量int cnt = 0, sz = right - left + 1;vector<int> tmp(sz, 0);int cur1 = left, cur2 = mid + 1;int i = 0;while (cur1 <= mid && cur2 <= right) {if (nums[cur1] <= nums[cur2])tmp[i++] = nums[cur1++];else {cnt += (mid - cur1 + 1);tmp[i++] = nums[cur2++];}}while (cur1 <= mid)tmp[i++] = nums[cur1++];while (cur2 <= right)tmp[i++] = nums[cur2++];// 复原数组for (i = 0; i < sz; i++) {nums[left + i] = tmp[i];}return ln + rn + cnt;}int reversePairs(vector<int>& record) {int ret = msort(record, 0, record.size() - 1);return ret;}
};

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

题目解析

在这里插入图片描述

这道题,和上一道逆序对的题可以说非常相似,逆序对那道题是让我们求逆序对的总数;

而这道题,则是让我们求出某一个位置右边有多少小于该位置的值的;也就是以某一位置为左边值,能组成逆序对的个数。

算法思路

对于这道题,整体思路是和求逆序对总数是相同的。

暴力解法:遍历数组,枚举所有的二元组,求出每一个位置右侧小于自己的元素个数。

分治-归并

和求逆序对一样,但是这道题要注意要求出每一个位置右侧有多少元素小于自己(也就是以某一个位置为左边值,能形成逆序对的个数

所以,在进行归并排序的过程中,我们不仅要对数组进行排序,并且还要维护下标。

这里使用降序排序,合并两个有序的数组部分时:

如果nums[cur1] > nums[cur2]时,区间[cur2, right]中的所有元素都能和nums[cur1]形成逆序对,此时找到nums[cur1]对应原始数组中下标,统计逆序对个数即可。

维护nums中每一个元素的值,以及它的原始下标;在排序的过程中,不仅要排序数组,还是更修对应的原始下标

代码实现

class Solution {
public:void msort(vector<int>& ret, vector<int>& nums, vector<int>& index,int left, int right) {if (left >= right)return;int mid = left + (right - left) / 2;msort(ret, nums, index, left, mid);msort(ret, nums, index, mid + 1, right);int sz = right - left + 1;int cur1 = left, cur2 = mid + 1, i = 0;vector<int> tmp_n(sz), tmp_i(sz);while (cur1 <= mid && cur2 <= right) {if (nums[cur1] <= nums[cur2]) {tmp_n[i] = nums[cur2];tmp_i[i++] = index[cur2++];} else {ret[index[cur1]] += (right - cur2 + 1);tmp_n[i] = nums[cur1];tmp_i[i++] = index[cur1++];}}while (cur1 <= mid) {tmp_n[i] = nums[cur1];tmp_i[i++] = index[cur1++];}while (cur2 <= right) {tmp_n[i] = nums[cur2];tmp_i[i++] = index[cur2++];}for (int i = 0; i < sz; i++) {nums[left + i] = tmp_n[i];index[left + i] = tmp_i[i];}}vector<int> countSmaller(vector<int>& nums) {int n = nums.size();vector<int> ret(n, 0), index(n);for (int i = 0; i < n; i++)index[i] = i;msort(ret, nums, index, 0, n - 1);return ret;}
};

四、翻转对

题目解析

在这里插入图片描述

这道题和上述逆序对的题稍稍不一样,这里如果i<j并且nums[i] > 2*nums[j](i,j)就是一个重要翻转对。

给定一个数组nums要我们求出数组中重要翻转对的数量。

算法思路

这道题的整体思路和上述是一模一样的,还是在归并排序的过程中去求重要翻转对的个数。

在分治-归并排序的过程,合并两个有序数组时,可以保证左边区间中的元素下标都是小于右边区间元素的下标的;

所以,只需要统计坐标区间中任意元素和右边区间中的任意元素,满足nums[i] > 2*nums[j]即可。

两个数组区间还是有序的 :(降序排序)如果nums[i] >nums[j]*2,则区间[j, right]中所有元素都可以和元素i形成一个重要翻转对。

至于左、右区间内的重要翻转对个数,在递归的过程中求出即可。

这里因为判断重要翻转对的条件时nums[i] > 2*nums[j],而排序数组的条件是nums[i] > nums[j]

所以,在归并排序的过程中,要先统计重要翻转对的个数,再合并两个有序的数组部分。

代码实现

class Solution {
public:int msort(vector<int>& nums, int left, int right) {if (left >= right)return 0;int mid = left + (right - left) / 2;int ln = msort(nums, left, mid);int rn = msort(nums, mid + 1, right);int cnt = 0, cur1 = left, cur2 = mid + 1;while (cur1 <= mid && cur2 <= right) {if (nums[cur1] <= 2 * (long long)nums[cur2])cur2++;else {cnt += (right - cur2 + 1);cur1++;}}int sz = right - left + 1, i = 0;cur1 = left, cur2 = mid + 1;vector<int> tmp(sz);while (cur1 <= mid && cur2 <= right) {if (nums[cur1] <= nums[cur2])tmp[i++] = nums[cur2++];elsetmp[i++] = nums[cur1++];}while (cur1 <= mid)tmp[i++] = nums[cur1++];while (cur2 <= right)tmp[i++] = nums[cur2++];for (i = 0; i < sz; i++)nums[left + i] = tmp[i];return cnt + ln + rn;}int reversePairs(vector<int>& nums) {return msort(nums, 0, nums.size() - 1);}
};

本篇文章到这里就结束了,感谢支持
我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=2oul0hvapjsws

http://www.dtcms.com/a/523868.html

相关文章:

  • 济南高新区 网站制作wordpress直接购买
  • 织梦网站首页幻灯片不显示新华美玉官方网站在线做
  • 蓝色星球如何打造能与企业共同进化的灵活系统
  • 【问题】在VSCode中设置conda的Python环境
  • NGINX 负载均衡应用实战:从配置到策略的深度解析
  • 关于buildroot文件系统中rootfs的内容,该怎么增删(瑞芯微rv1126b)
  • Qt 入门简洁笔记:信号与槽
  • 算法入门---专题二:滑动窗口2(最大连续1的个数,无重复字符的最长子串 )
  • 手机摄像头如何识别体检的色盲检查图的数字和图案(下)
  • 政务系统信创改造中,金仓日志如何满足等保2.0三级审计要求
  • 记录一个监控网卡某个IP发送流量
  • Python-UnitTest框架
  • 模型-模型压缩:量化、剪枝、蒸馏、二值化
  • UE5 蓝图-12:pawn蓝图,轴映射-鼠标右键,补充轴映射与操作映射的区别。相机的旋转俯仰逻辑,伸缩逻辑,浮点差值函数 FInterpTo;
  • Vcenter7使用主机配置文件重置ESXI主机 root 密码
  • STL 容器:List
  • 做网站销售好累网上开店的货源渠道有哪些
  • 图像,视频Lora模型训练的Timestep Type时间步类型
  • 告别云端依赖!ComfyUI本地化视频生成实战教程+cpolar实战
  • Android16之如何获取APP、Bin进程的UID(二百六十三)
  • 在JavaScript / HTML中,无法通过开发者工具查看DOM元素中input里输入的密码
  • 像素塔防游戏:像素守卫者
  • 什么是栈?深入理解 JVM 中的栈结构
  • Go Web 编程快速入门 07.2 - 模板(2):解析与执行(含Demo)
  • 公司用wordpress建站用花钱大连网站设计开发
  • 建设网站需要下载神呢软件吗重庆企业网站推广公司
  • 常规面光源在工业视觉检测上的应用
  • 数据结构——直接插入排序
  • 如何开公司做网站素材免费网站
  • Spring Boot 配置优先级