算法专题七: 分治归并
目录
- 1. 排序数组
- 2. 交易逆序对的总数
- 3. 计算右侧小于当前元素的个数
- 4. 翻转对
1. 排序数组


算法思路: 本道题使用归并的思路进行排序, 先讲数组分为左右两个区间, 然后合并两个有序数组.
class Solution {vector<int> tmp;
public:vector<int> sortArray(vector<int>& nums) {tmp.reserve(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 = (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];}}
};
2. 交易逆序对的总数

算法思路:

先找左半部分的逆序对, 然后再找右半部分的逆序对, 最后再一左一右的找逆序对, 找完左半部分和右半部分之后给数组排个序对数组的结果不影响, 而且排完序之后找一左一右的逆序对还简单些, 不由得我们就可以想到此方法和归并排序的思路一样, 只不过再排序的过程中增加了统计逆序对的个数. 这里可以采用两种策略, 第一种固定当前位置的值, 然后找前面有多少数是比我大的, 第二种策略, 固定当前位置的值, 找后面有多少元素是比我小的.
- 首先使用第一种策略, 固定一个数, 在前面找比我大的元素的个数.

当前为升序的排序, 我们归并的时候如果nums[cur1] <= nums[cur2] 时, 此时我们只需要把cur1++就行, 如果nums[cur1]在某一个位置大于nums[cur2]时,此时已经找到了比nums[cur2]大的元素, 那么mid- cur1 + 1这段区间里面的所有值都比nums[cur2]大, 此时统计逆序对的数量即可, 然后再让cur2++, 但是不要往了我们还要排序, 所以这一步写在归并数组那里即可.
那么既然升序可以, 降序可不可以呢?

如图所示, 该数组为降序的时候, 固定cur1, cur2位置时,如果cur1大于cur2则开始统计cur1中大于cur2元素的个数, 看似没有什么问题, 但是如果进入下一次循环中, cur1++之后还有可能大于cur2,此时在统计那不是重复了嘛, 显然结果是错误的
- 第二种策略: 固定一个数, 然后找后面比我小的元素的个数

首先先来看降序的数组, 来统计固定一个位置之后, 后面有多少元素比我小, 当nums[cur1] <= nums[cur2]时, 此时不是我们想要的结果, 我们需要的是nums[cur1]>nums[cur2], 这个时候, 我们统计后面有多少个元素比我小即可, 很显然此时数组是降序的, 所以right - cur2 + 1 即为结果
if(nums[cur1] <= nums[cur2])
tmp[i++] = nums[cur2++];
else
{
ret += right - cur2 + 1;
tmp[i++] = nums[cur1++];
}
反之我们来看一下升序

此时数组为升序, 我们需要找后面比前面小的元素的个数, 当固定到cur1之后, nums[cur1] > nums[cur2]时, 此时开始找结果, 在后面看在mid到cur2之间的这部分都是比nums[cur1]小的, 但是下一次cur2++之后还有可能是小于nums[cur1]的, 所以此时重复统计了 , 结果是错误的.
代码部分:
策略1
class Solution {int tmp[50000];
public:int reversePairs(vector<int>& record) {return mergeSort(record,0,record.size()-1);}int mergeSort(vector<int>& nums,int left,int right){if(left>=right) return 0;int ret = 0;int mid = (right + left) >> 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 i = left; i <= right; i++){nums[i] = tmp[i - left];}return ret;}
};
策略2
class Solution {int tmp[50000];
public:int reversePairs(vector<int>& record) {return mergeSort(record,0,record.size()-1);}int mergeSort(vector<int>& nums,int left,int right){if(left>=right) return 0;int ret = 0;int mid = (right + left) >> 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[cur2++];else{ret += right - cur2 + 1;tmp[i++] = nums[cur1++]; }}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;}
};
3. 计算右侧小于当前元素的个数

算法思路:
本道题更上一道题的思路是一模一样的, 上一道题我们采用了策略一的方法, 本道题我们采用策略二, 找后面比当前位置元素小的元素的个数, 但是与上一道题不同的是本道题要求返回对应位置的结果, 这是个问题, 如何解决呢, 我们可以采用哈希的思想, 如果使用哈希表那么如果遇到重复的元素, 我们就无法进行解决, 但是我们可以采用这种思想, 创建一个新的数组, 这个数组中存放的是对应位置原始的下标, 然后这个数组跟着nums一起变化, 但是无论怎样变换, 里面的下标还是最原始的下标位置.

编写代码:
class Solution {vector<int> index;vector<int> ret;int tmpNums[500010];int tmpIndxt[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)>>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]){tmpNums[i] = nums[cur2];tmpIndxt[i++] = index[cur2++];}else{ret[index[cur1]] += right - cur2 + 1;tmpNums[i] = nums[cur1];tmpIndxt[i++] = index[cur1++];}}while(cur1 <= mid){tmpNums[i] = nums[cur1];tmpIndxt[i++] = index[cur1++];}while(cur2 <= right){tmpNums[i] = nums[cur2];tmpIndxt[i++] = index[cur2++];}for(int i = left; i <= right; i++){nums[i] = tmpNums[i - left];index[i] = tmpIndxt[i - left];}}
};
4. 翻转对

算法思路:
首先我们还是先考虑两个策略. 策略一, 计算后面有多少个元素的两倍比我小, 策略二. 计算前面有多少元素的一半比我大. 不同于前两题, 本道题无法在归并的时候进行计算, 因为这里的比较逻辑和归并的比较逻辑是不同的, 所以这里我在归并逻辑之前采用双指针的方法先进行统计, 然后在进行归并排序.


那么首先来看策略一, 在处理完左右之后并排序之后, 我们处理一左一右的情况, 使用双指针, 计算后面有多少元素的两倍比我小, 如果nums[cur1] <= nums[cur2] * 2, 则说明后面元素的两倍比我大, 那么继续移动cur2, 知道找到cur2的位置两倍比我小, 然后进行统计, 此时cur1++, 进行下一次的统计, 这里cur2还需要回退到mid+1的位置嘛?
答案是不需要的, 因为当前位置cur2位置前面元素的两倍都是比我大的, 那cur1++之后降序, 那么一定也是比我大的, 所以直接统计就可以了, 所以整体的时间复杂度为O(NlogN).
class Solution {int tmp[50000];
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;while(cur1 <= mid){while(cur2 <= right && nums[cur2] >= nums[cur1] / 2.0) cur2++;if(cur2 > right) break;//小优化ret += right - cur2 + 1;cur1++;}//降序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++];for(int j = left; j <= right; j++)nums[j] = tmp[j]; return ret;}
};
策略二: 找前面有多少元素的一半比我大

其实更策略一的逻辑是类似的, 只不过这里的数组是升序排列的, 找前面有多少元素的一半比我大, 先让cur1走, 如果/2之后大于nums[cur2]则, cur1++, 找到位置之后统计ret, 然后让cur2++,进行下一次的统计, 此时cur1也不需要回去, cur1之前的位置一半都是比cur2小的, 那么cur2++之后一定还是比cur1小, 所以cur2直接++即可.
class Solution {int tmp[50000];
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 mid = (left + right) >> 1;int ret = 0;ret += mergeSort(nums,left,mid);ret += mergeSort(nums,mid+1,right);int cur1 = left, cur2 = mid + 1, i = left;//升序, 先统计逆序对while(cur2 <= right){while(cur1 <= mid && nums[cur1] / 2.0 <= nums[cur2]) 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++];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;}
};
完
相关文章:
算法专题七: 分治归并
目录 1. 排序数组2. 交易逆序对的总数3. 计算右侧小于当前元素的个数4. 翻转对 1. 排序数组 算法思路: 本道题使用归并的思路进行排序, 先讲数组分为左右两个区间, 然后合并两个有序数组. class Solution {vector<int> tmp; public:vector<int> sortArray(vector&…...
一个基于vue功能强大的表格组件--vxe-table的二次封装
基础使用 一个基于 vue 的 PC 端表格组件,支持增删改查、虚拟滚动、懒加载、快捷菜单、数据校验、树形结构、打印导出、表单渲染、数据分页、虚拟列表、模态窗口、自定义模板、渲染器、贼灵活的配置项、扩展接口等… <vxe-grid v-bind"gridOptions1"…...
CSS网页布局(重塑网页布局)
一、实现两列布局 许多网站有一些特点,如页面顶部放置一个大的导航或广告条,右侧是链接或图片,左侧放置主要内容,页面底部放置版权信息等。 一般情况,此类网页布局的两列都有固定的宽度,而且从内容上很容易…...
计算机网络:数据链路层 —— 以太网(Ethernet)
文章目录 局域网局域网的主要特征 以太网以太网的发展100BASE-T 以太网物理层标准 吉比特以太网载波延伸物理层标准 10吉比特以太网汇聚层交换机物理层标准 40/100吉比特以太网传输媒体 局域网 局域网(Local Area Network, LAN)是一种计算机网络&#x…...
考研前所学c语言02(2024/10/16)
1.一个十进制的数转化为二进制的就是不断除二取余,得到的余数从下到上取 比如123: 结果为: 同理其他的十进制转八进制,十六进制就除八,除十六即可 再比如123转十六进制: 因为余数是11,十六进…...
R语言绘图——坐标轴及图例
掌握坐标轴与图例的设置与调整,对于提升数据可视化的清晰度和可读性至关重要。通过这些工具,可以有效地传达数据背后的故事,提高图表的表现力。 0x01 坐标轴 一、坐标轴的设置 1、修改坐标轴的标签 在ggplot2中,坐标轴是根据数…...
JDK中socket源码解析
目录 1、Java.net包 1. Socket通信相关类 2. URL和URI处理类 3. 网络地址和主机名解析类 4. 代理和认证相关类 5. 网络缓存和Cookie管理类 6. 其他网络相关工具类 2、什么是socket? 3、JDK中socket核心Api 4、核心源码 1、核心方法 2、本地方法 3、lin…...
Ansible自动化运维项目实战指南
Ansible自动化运维项目实战指南 在当今快速发展的IT环境中,运维工作的复杂性和规模性日益增加,传统的手动运维方式已难以满足高效、可靠、可重复性的需求。Ansible作为一款开源的自动化运维工具,凭借其简单易用、无需代理、基于SSH的架构特性…...
MySQL【知识改变命运】10
联合查询 0.前言1.联合查询在MySQL里面的原理2.练习一个完整的联合查询2.1.构造练习案例数据2.2 案例:⼀个完整的联合查询的过程2.2.1. 确定参与查询的表,学⽣表和班级表2.2.2. 确定连接条件,student表中的class_id与class表中id列的值相等2.…...
Java学习教程,从入门到精通, Java 基础语法(4)
1、Java 基础语法 一、Java 简介与开发环境搭建 Java 简介:Java 是一种面向对象的编程语言,具有跨平台、安全、稳定等特点。Java 主要应用于企业级应用、Android 应用开发、大数据处理等领域。开发环境搭建:搭建 Java 开发环境需要安装 JDK…...
反编译工具-Jclasslib的使用,与Java方法调用的探索
这里写目录标题 前言IDEA下查看字节码的两种方法使用idea自带的插件工具安装插件 为什么没有看出方法调用关系原因分析工厂举例 知识补充语言java可移植性 总结 前言 画时序图的时候,我想验证下方法的调用是否写的正确。方法调用不仅涉及到程序的基本逻辑流程&#…...
力扣 简单 876.快慢指针
文章目录 题目介绍题解 题目介绍 题解 class Solution {public ListNode middleNode(ListNode head) {ListNode slow head, fast head;while(fast ! null && fast.next ! null){slow slow.next;fast fast.next.next;}return slow;} }...
FineReport 计算同比增长
1、数据库查询 SELECTt1.年,t1.月,t1.总金额 AS 同期金额,t1.仓库名称,t2.总金额 AS 上期金额 FROMtest t1LEFT JOIN test t2 ON ( t1.年 t2.年 1 ) AND t1.月 t2.月 AND t1.仓库名称 t2.仓库名称2、配置字段 月份字段加后缀 月 数据列加后缀 计算同比增长率 if(LEN(B3)0 …...
从0开始深度学习(12)——多层感知机的逐步实现
依然以Fashion-MNIST图像分类数据集为例,手动实现多层感知机和激活函数的编写,大部分代码均在从0开始深度学习(9)——softmax回归的逐步实现中实现过 1 读取数据 import torch from torchvision import transforms import torchv…...
如何利用OpenCV和yolo实现人脸检测
在之前的blog里面,我们有介绍OpenCV和yolo的区别,本文就人脸检测为例,分别介绍下OpenCV和yolo的实现方式。 OpenCV实现人脸检测 一、安装 OpenCV 首先确保你已经安装了 OpenCV 库。可以通过以下方式安装: 使用包管理工具安装&…...
015集——c# 实现CAD excel交互(CAD—C#二次开发入门)
第一步:添加引用 程序集—>扩展 namespace WindowsFormsApp2 {public partial class Form1 : Form{public Form1(){InitializeComponent();}private void Form1_Load(object sender, EventArgs e){}private void 获取当前excel_Click(object sender, EventArgs e…...
【计网笔记】以太网
经典以太网 总线拓扑 物理层 Manchester编码 数据链路层 MAC子层 MAC帧 DIX格式与IEEE802.3格式 IEEE802.3格式兼容DIX格式 前导码(帧开始定界符SOF) 8字节 前7字节均为0xAA第8字节为0xAB前7字节的Manchester编码将产生稳定方波,用于…...
Java 入门基础篇14 - java面向对象思想以及特性
学习目标: 一、目标 面向对象思想类和对象对象的创建和使用属性和方法封装 开始学习: 二、编程思想 2.1 什么是编程思想 做人有做人的原则,编程也有编程的原则。这些编程的原则,就叫做编程思想。 2.2 面向过程和面向对象 二…...
第15篇:网络架构优化与综合案例分析
目录 引言 15.1 网络性能优化的方法与工具 15.1.1 带宽管理与流量控制 15.1.2 负载均衡 15.1.3 缓存优化 15.2 网络故障的排查与解决 15.2.1 常用的网络故障排查工具 15.2.2 网络故障排查案例 15.3 网络安全架构的综合设计案例 15.3.1 企业网络安全架构的要求 15.3.…...
UI自动化测试实战
补充:Selenium主要用于Web页面的自动化测试,它可以模拟用户的各种操作,如点击、输入、滚动等,来测试网页的功能。而Appium是一个开源的移动端自动化测试工具。 一、自动化测试实战章节 自动化测试流程测试用例编写项目自动化测试…...
Bilibili API Python客户端深度解析与实战指南
Bilibili API Python客户端深度解析与实战指南 【免费下载链接】bilibili-api 哔哩哔哩常用API调用。支持视频、番剧、用户、频道、音频等功能。原仓库地址:https://github.com/MoyuScript/bilibili-api 项目地址: https://gitcode.com/gh_mirrors/bi/bilibili-ap…...
STM32H7 SPI4 FLASH HAL库配置优化实践
1. STM32H7 SPI4与FLASH通信基础 最近在做一个基于STM32H743IIT6的项目时,遇到了SPI4与FLASH通信的配置问题。SPI4工作在50MHz的高时钟频率下,调试过程中发现了一些有趣的细节。比如分频系数低于SPI_BAUDRATEPRESCALER_8时读取就会失败,而高于…...
深入理解 C# 架构思维:继承的界限、多态的解耦与属性的封装
C#学习笔记面向对象编程:继承什么是继承继承的语法方法的重写构造函数的重载与 base 关键字动物世界完整实例踩坑汇总面向对象编程:多态多态的实现步骤踩坑汇总面向对象编程:封装核心套路:私有字段 公开属性代码实例踩坑汇总面向…...
告别Frida注入:手把手教你用IDA和010 Editor修改TikTok的libsscronet.so实现抓包(Android 30.8.4)
静态逆向实战:不依赖Frida修改TikTok核心通信模块实现抓包 在移动安全研究领域,动态注入工具如Frida一直是分析应用协议的主流选择。但当面对TikTok这类采用自研通信协议的应用时,频繁的版本更新会导致动态注入方案需要持续维护。本文将展示一…...
实战演练:基于快马平台快速构建kafka电商用户行为分析系统
实战演练:基于快马平台快速构建Kafka电商用户行为分析系统 最近在做一个电商数据分析项目,需要实时追踪用户的点击和浏览行为。经过调研发现,Kafka作为分布式消息队列非常适合这种高吞吐量的场景。下面分享我是如何用InsCode(快马)平台快速搭…...
gallery性能分析工具:找出本地AI平台的性能瓶颈
gallery性能分析工具:找出本地AI平台的性能瓶颈 【免费下载链接】gallery A gallery that showcases on-device ML/GenAI use cases and allows people to try and use models locally. 项目地址: https://gitcode.com/GitHub_Trending/gallery44/gallery 在…...
PyTorch系列 —— 深入解析nn.Module与nn.Linear的魔法调用机制
1. 从魔法调用开始:为什么m(input)能直接计算? 第一次看到m nn.Linear(20, 30)后面跟着output m(input)这种写法时,我盯着屏幕愣了三秒——这明明是个类实例,怎么可以直接当函数用?后来才发现,这正是PyTo…...
运算放大器基础:从符号到负反馈的实战解析
1. 运算放大器基础认知 第一次接触运算放大器时,我盯着电路板上那个小小的三角形符号发愣——这玩意儿凭什么能同时处理比较和放大两种任务?后来才发现,它的强大之处恰恰藏在最简单的符号里。运放的符号主体是个三角形,五个关键引…...
如何高效管理ExHentai漫画收藏:终极标签化管理解决方案
如何高效管理ExHentai漫画收藏:终极标签化管理解决方案 【免费下载链接】exhentai-manga-manager ExHentai本地漫画标签管理阅读应用, ExHentai local manga tag-manager and reader 项目地址: https://gitcode.com/gh_mirrors/ex/exhentai-manga-manager 你…...
3步搞定精准歌词:LDDC歌词工具全方位解决方案
3步搞定精准歌词:LDDC歌词工具全方位解决方案 【免费下载链接】LDDC 简单易用的精准歌词(逐字歌词/卡拉OK歌词)下载匹配工具|A simple and user-friendly tool for downloading and matching precise lyrics (word-by-word lyrics/Karaoke lyrics) 项目地址: http…...
