【算法专场】分治(下)

目录
前言
归并排序
思想
912. 排序数组
算法思路
算法代码
LCR 170. 交易逆序对的总数
算法思路
算法代码
315. 计算右侧小于当前元素的个数 - 力扣(LeetCode)
算法思路
算法代码
493. 翻转对
算法思路
算法代码
好久不见~时隔多日,继续来更新我们的算法咯hh~
前言
在算法专栏上一篇,我们讲解了分治是什么,如何利用分治的思想来解决一些排序问题或者找指定元素,而在上篇中,我们主要讲的是利用快排,那么本篇我们就来讲解一下归并排序。
归并排序在我数据结构专栏中有涉及,想要了解的更详细的可以去观看。
归并排序
思想
归并排序也是利用了分治的思想,将一个序列化成一个个子序列,在每个子序列上进行排序,再将已经排好的子序列进行合并,就能得到一个有序的序列。

归并排序和快速排序虽然都是利用了分治的思想,但两者排序的时机不同:
快速排序是在序列分割成一个个子序列的过程同时对子序列进行排序的;
而归并排序则是将序列分成一个个子序列之后,在序列合并前进行排序。
简单来说:快排是在分割时就进行排序,归并则是在合并的过程中进行排序。
912. 排序数组

这道题虽然在上一篇中已经讲过,但使用的是快排解决,本篇我们就用归并来解决这道题。
算法思路
这里我们采用归并的递归方法来解决。
- 辅助数组:辅助数组主要是为了存储已经排好序的数组。
- 创建归并函数:我们需要三个参数,分别是数组、每次递归的左边界left、右边界right。当left>=right时,说明数组已经排完序,此时可以直接返回。若还没有排完序,我们需要对数组进行分割排序。当分割完成后,我们就可以借助辅助数组,对数组进行排序。
算法代码
public class Solution {// 临时数组,用于归并排序时暂时存储元素int[] tmp;/*** 对数组进行排序** @param nums 待排序的数组* @return 排序后的数组*/public int[] sortArray(int[] nums) {// 初始化临时数组tmp = new int[nums.length];// 调用归并排序mergeSort(nums, 0, nums.length - 1);return nums;}/*** 归并排序算法** @param nums 待排序的数组* @param left 排序数组的起始位置* @param right 排序数组的结束位置*/private void mergeSort(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 i = left, j = mid + 1;int cnt = 0;// 合并左右两部分数组while (i <= mid && j <= right) {// 将较小的元素放入临时数组if (nums[i] < nums[j]) {tmp[cnt++] = nums[i++];} else {tmp[cnt++] = nums[j++];}}// 处理左半部分剩余的元素while (i <= mid) {tmp[cnt++] = nums[i++];}// 处理右半部分剩余的元素while (j <= right) {tmp[cnt++] = nums[j++];}// 将排序后的元素从临时数组复制回原数组for (int k = left; k <= right; k++) {nums[k] = tmp[k - left];}}
}
时间复杂度为O(n*logn),空间复杂度为O(n).
LCR 170. 交易逆序对的总数

算法思路
这道题其实也是可以用归并排序来解决,在我们归并排序合并左右数组的时候,如果左区间(在合并的时候,左右数组已经是有序的了)中的元素大于右区间的元素,那么此时我们就可以知道逆序对有【mid-left+1】对。
示例:(画的有点抽象)

算法代码
public class Solution {// 计数器,用于统计逆序对的总数int count=0;// 临时数组,用于归并排序时暂存数据int[] tmp;/*** 计算逆序对的总数** @param record 输入的数组* @return 逆序对的总数*/public int reversePairs(int[] record) {// 初始化临时数组tmp=new int[record.length];// 调用归并排序mergeSort(record,0,record.length-1);// 返回逆序对的总数return count;}/*** 归并排序算法** @param nums 待排序的数组* @param left 排序数组的起始位置* @param right 排序数组的结束位置*/private void mergeSort(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 i = left, j = mid + 1;int cnt = 0;// 合并左右两部分数组while (i <= mid && j <= right) {// 将较小的元素放入临时数组if (nums[i] < nums[j]) {tmp[cnt++] = nums[i++];} else {// 当右半部分的元素小于左半部分的元素时,说明找到了逆序对count+=mid-i+1;tmp[cnt++] = nums[j++];}}// 处理左半部分剩余的元素while (i <= mid) {tmp[cnt++] = nums[i++];}// 处理右半部分剩余的元素while (j <= right) {tmp[cnt++] = nums[j++];}// 将排序后的元素从临时数组复制回原数组for (int k = left; k <= right; k++) {nums[k] = tmp[k - left];}}
}
时间复杂度为O(n*logn),空间复杂度为O(n).
315. 计算右侧小于当前元素的个数 - 力扣(LeetCode)

算法思路
这道题是要求每个元素后面有多个比当前元素少的个数,我们可以用归并排序来解决,在归并的时候进行计数。
1. 初始化:
我们定义四个数组ret、index、tmpNums、tmpIndex。ret数组用于存储原数组每个元素右侧小于当前元素的个数;index用来存储原始数组元素的下标;tmpNums用于存储归并排序完的元素,而tmpIndex则是存储排序完每个元素对应的原始下标位置。
2. 归并和计数:
- 定义一个merge方法,不需要返回值。在merge方法中,需要nums,数组的左右边界left、right;
- 如果left>=right时或者子数组的长度只有1时,则直接返回,此时不需要进行计数。
- 如果left<right,那么就将数组分割成两部分,继续调用merge方法进行递归;在合并左右两个子数组的时候,我们需要借助两个指针cur1和cur2,cur1和cur2指向左子数组和右子数组的起始位置,用一个i当做临时数组的下标。
- 在循环whlie(cur1<=mid&&cur2<=right)中,比价左子数组和右子数组的元素:
- 如果nums[cur1]<=nums[cur2],将右子数组的当前元素放入到临时数组中,同时将对应的下标放入到tmpIndex中,再将i和cur2往后移;
- 如果nums[cur1]>nums[cur2],由于我们这里对数组是进行降序操作,所以说明从[cur2,right]这个区间上所有元素都是小于nums[cur1]的,即nums[cur1]右侧有right-cur2+1个元素小于它,将这个数量累加到ret[index[cur1]]中。再将nums[cur1]放入到临时数组tmpNums中,同时将对应的原始下标放入到tmpIndex中,再将i和cur1往后移。
- 当跳出循环后,说明左右子数组中其中有一个全部放入到临时数组中,但其中还有一个子数组没有放入临时数组中,我们利用两个循环来处理左子数组和右子数组的剩余元素。
- 当把数组全部放到临时数组后,我们再将临时数组中的元素复制回nums和index数组中。
算法代码
int[] ret;//用来存放结果int[] index;//用来存放原始数组的下标int[] tmpNums;//辅助数组,用来存储排序后的数组int[] tmpIndex;//辅助数组,用来存储排序后的数组的下标public List<Integer> countSmaller(int[] nums) {int n = nums.length;ret = new int[n];index = new int[n];tmpNums = new int[n];tmpIndex = new int[n];for(int i=0;i<n;i++){index[i]=i;}merge(nums,0,n-1);List<Integer> list = new ArrayList<>();for(int num:ret){list.add(num);}return list;}private void merge(int[] nums,int left,int right){if(left>=right) return;int mid = left+(right-left)/2;merge(nums,left,mid);merge(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(i=left;i<=right;i++){nums[i]=tmpNums[i-left];index[i]=tmpIndex[i-left];}}
时间复杂度为(nlogn),空间复杂度为O(n)
493. 翻转对

算法思路
本道题可以用归并的方法来解决,题目要求我们找翻转对,即满足i<j,并且nums[i[>2*nums[j]。
我们可以想成nums[i]元素/2.0后,比nums[j]还要大,这样的话,我们采用归并降序就好解多了。
- 初始化:定义一个临时数组tmp,以及一个变量ans,用来存储翻转对的个数;
- 归并:
- 定义一个merge方法,在此方法中,如果left>=right,那么就直接返回
- 如果left<right,那么就继续对数组进行分割为两部分递归调用merge方法。
- 定义三个变量cur1、cur2、index,cur1和cur2分别指向左右子数组的起始位置。index用做临时数组的下标
- 计算翻转对数量:
- 在循环while(cur1<=mid)中,让左数组中的元素nums[cur1],和右子数组中每个元素nums[cur2]进行比较,查看满足条件nums[cur1]/2.0<=nums[cur2]的位置,如果找到,说明从cur2到right这段区间上的元素都能和nums[cur1]构成翻转对。将right-cur2+1累加到ans中,再让cur1++,查找下一个左子数组元素的翻转对。
- 合并两个有序的子数组:套路和上一题差不多,都是类似写法
算法代码
// 临时数组,用于归并排序时的合并操作
int[] tmp;
// 记录翻转对的数量
int ans;/*** 计算数组中的翻转对数量。翻转对定义为:对于下标 i < j,如果 nums[i] > 2 * nums[j],则 (i, j) 是一个翻转对。** @param nums 输入的整数数组* @return 翻转对的数量*/
public int reversePairs(int[] nums) {tmp = new int[nums.length];mergeSort(nums, 0, nums.length - 1);return ans;
}/*** 使用归并排序的方法对数组进行排序,并在排序过程中统计翻转对的数量。** @param nums 需要排序的数组* @param left 当前子数组的左边界* @param right 当前子数组的右边界*/
public void mergeSort(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;while (cur1 <= mid) {while (cur2 <= right && nums[cur1] / 2.0 <= nums[cur2]) cur2++;if (cur2 > right) break;ans += right - cur2 + 1;cur1++;}// 合并左右两个已排序的子数组cur1 = left;cur2 = mid + 1;int index = left;while (cur1 <= mid && cur2 <= right) {tmp[index++] = nums[cur1] <= nums[cur2] ? nums[cur2++] : nums[cur1++];}while (cur1 <= mid) tmp[index++] = nums[cur1++];while (cur2 <= right) tmp[index++] = nums[cur2++];for (int i = left; i <= right; i++) nums[i] = tmp[i];
}
时间复杂度为O(n*logN),空间复杂度为O(n)

以上就是本篇所有内容~
若有不足,欢迎指正~
相关文章:
【算法专场】分治(下)
目录 前言 归并排序 思想 912. 排序数组 算法思路 算法代码 LCR 170. 交易逆序对的总数 算法思路 算法代码 315. 计算右侧小于当前元素的个数 - 力扣(LeetCode) 算法思路 算法代码 493. 翻转对 算法思路 算法代码 好久不见~时隔多日&…...
OSPF基础(2):数据包详解
OSPF数据包(可抓包) OSPF报文直接封装在IP报文中,协议号89 头部数据包内容: 版本(Version):对于OSPFv2,该字段值恒为2(使用在IPV4中);对于OSPFv3,该字段值恒为3(使用在IPV6中)。类型(Message Type):该OSPF报文的类型。…...
ubuntu直接运行arm环境qemu-arm-static
qemu-arm-static 嵌入式开发有时会在ARM设备上使用ubuntu文件系统。开发者常常会面临这样一个问题,想预先交叉编译并安装一些应用程序,但是交叉编译的环境配置以及依赖包的安装十分繁琐,并且容易出错。想直接在目标板上进行编译和安装&#x…...
Docker Desktop安装kubernetes时一直在Starting:Kubernetes failed to start
原因:由于墙的问题,导致拉取国外的K8s镜像失败 解决: 下载 k8s-for-docker-desktop 选中自己的kubernetes 版本 下载zip包 PowerShell运行load_images.ps1文件 重启docker kubernetes运行成功...
beyond the ‘PHYSICAL‘ memory limit.问题处理
Container [pid5616,containerIDcontainer_e50_1734408743176_3027740_01_000006] is running 507887616B beyond the ‘PHYSICAL’ memory limit. Current usage: 4.5 GB of 4 GB physical memory used; 6.6 GB of 8.4 GB virtual memory used. Killing container. 1.增大map…...
AI大模型零基础学习(1):大模型使用篇
一、大模型是什么?为什么你需要它? 一句话理解:大模型像一个能听懂人话的"超级智能助手",它能写文章、解数学题、翻译语言、写代码…只要你会打字提问,它就能给出答案。 典型场景: 学生党&…...
StarSpider 星蛛 爬虫 Java框架 可以实现 lazy爬取 实现 HTML 文件的编译,子标签缓存等操作
StarSpider 星蛛 爬虫 Java框架 开源技术栏 StarSpider 能够实现 针对 HTML XSS SQL 数学表达式等杂乱数据的 爬取 解析 提取 需求! 目录 文章目录 StarSpider 星蛛 爬虫 Java框架目录介绍如何获取?maven配置 架构是什么样的?结果对象的类…...
【翻译+论文阅读】DeepSeek-R1评测:粉碎GPT-4和Claude 3.5的开源AI革命
目录 一、DeepSeek-R1 势不可挡二、DeepSeek-R1 卓越之处三、DeepSeek-R1 创新设计四、DeepSeek-R1 进化之路1. 强化学习RL代替监督微调学习SFL2. Aha Moment “啊哈”时刻3. 蒸馏版本仅采用SFT4. 未来研究计划 部分内容有拓展,部分内容有删除,与原文会有…...
先进制造aps专题二十八 生产排程仿真引擎和工厂生产仿真引擎的设计
一 排产仿真引擎的设计 主要分为仿真模型,仿真模型逻辑和仿真框架这三个部分 1 仿真模型 和算法排产不一样,在算法排产里,机器对应的是数据库记录,排产逻辑是写在整体的算法里的,而仿真排产,机器对应的是…...
WPF模板
WPF模板深度解析:打造个性化UI的利器 在WPF(Windows Presentation Foundation)的世界里,模板(Template)是构建个性化用户界面(UI)不可或缺的工具。它们允许开发者将控件的逻辑功能与…...
动态规划LeetCode-121.买卖股票的最佳时机1
给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。 你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。 返回你可以从这笔交易中获取的最大利润。…...
pytest+request+yaml+allure 接口自动化测试全解析[手动写的跟AI的对比]
我手动写的:Python3:pytest+request+yaml+allure接口自动化测试_request+pytest+yaml-CSDN博客 AI写的:pytest+request+yaml+allure 接口自动化测试全解析 在当今的软件开发流程中,接口自动化测试扮演着至关重要的角色。它不仅能够提高测试效率,确保接口的稳定性和正确性…...
单片机通讯中的时序图:初学者的入门指南
一、什么是时序图? 在单片机的世界里,时序图是一种非常重要的工具,它用于描述信号在时间上的变化规律。简单来说,时序图就像是信号的“时间线”,它展示了各个信号线在不同时间点上的电平状态。通过时序图,我…...
#渗透测试#批量漏洞挖掘#微商城系统 goods SQL注入漏洞
免责声明 本教程仅为合法的教学目的而准备,严禁用于任何形式的违法犯罪活动及其他商业行为,在使用本教程前,您应确保该行为符合当地的法律法规,继续阅读即表示您需自行承担所有操作的后果,如有异议,请立即停…...
Lua中文语言编程源码-第十一节,其它小改动汉化过程
__tostring 汉化过程 liolib.c metameth[] {"__转换为字符串", f_tostring}, lauxlib.c luaL_callmeta(L, idx, "__转换为字符串") lua.c luaL_callmeta(L, 1, "__转换为字符串") __len 汉化过程 ltm.c luaT_eventname[] ltablib.c c…...
import { Component, Vue, Prop, Watch } from ‘vue-property-decorator‘
文章目录 导入部分的解释总结Vue 3 的推荐替代方案总结 你提供的代码片段是使用 vue-property-decorator 库的示例,这是一个第三方库,它提供了 Vue 组件的装饰器,使得编写类风格的 Vue 组件更加方便。以下是对代码中每个部分的详细解释&…...
C++基础系列【5】namespace using
本文主要介绍namespace和using。 什么是namespace? namespace是指命名空间,表示某个变量标识符的可见空间,比如下面的代码: namespace Meow {int k 100; }int main() {std::cout << k << std::endl; }这段代码中在…...
MySQL万能备份脚本
此脚本适用于 MySQL 各个生命周期的版本 #!/bin/bash # mybackup.sh# 备份保留天数,建议保留三天 days7 # 备份时间 time$(date %Y%m%d%H%M%S) # 备份保存路径 backup_dir/opt/backup # 备份工具 toolmysqldump # 端口 port"3306" # 是否采用 --all-data…...
分桶函数的使用
除了 NTILE 函数,SQL 中还有其他一些与 分桶(bucketization)相关的函数,虽然它们的实现方式不同,但都涉及将数据分成多个区间或组。以下是一些常用的分桶函数: 1. CASE 语句 虽然 CASE 不是开窗函数&…...
5. k8s二进制集群之ETCD集群部署
下载etcd安装包创建etcd配置文件准备证书文件和etcd存储目录ETCD证书文件安装(分别对应指定节点)创建证书服务的配置文件启动etcd集群验证etcd集群状态继续上一篇文章《k8s二进制集群之ETCD集群证书生成》下面介绍一下etcd证书生成配置。 下载etcd安装包 https://github.com…...
JMeter通过BeanShell写入CSV文件中的中文乱码
在 JMeter 中通过 BeanShell 写入 CSV 文件时,如果出现中文乱码问题,通常是因为文件编码不匹配。默认情况下,FileWriter 使用的是系统默认编码(可能是 ISO-8859-1 或其他非 UTF-8 编码),而中文字符需要 UTF…...
智能化转型2.0:从“工具应用”到“价值重构”
过去几年,“智能化”从一个模糊的概念逐渐成为企业发展的核心议题。2024年,随着生成式AI、大模型、智能体等技术的爆发式落地,中国企业正式迈入智能化转型的2.0时代。这一阶段的核心特征是从单一场景的“工具应用”转向全链条的“价值重构”&…...
X Window System 架构概述
X Window System 架构概述 1. X Server 与 X Client 这里引入一张维基百科的图,在Linux系统中,若用户需要图形化界面,则可以使用X Window System,其使用**Client-Server**架构,并通过网络传输相关信息。 X…...
【ArcGIS Pro 简介1】
ArcGIS Pro 是由 Esri (Environmental Systems Research Institute)公司开发的下一代桌面地理信息系统(GIS)软件,是传统 ArcMap 的现代化替代产品。它结合了强大的空间分析能力、直观的用户界面和先进的三维可视化技术…...
启明星辰发布MAF大模型应用防火墙产品,提升DeepSeek类企业用户安全
2月7日,启明星辰面向DeepSeek等企业级大模型业务服务者提供的安全防护产品——天清MAF(Model Application Firewall)大模型应用防火墙产品正式发布。 一个新赛道将被开启…… DeepSeek的低成本引爆赛道规模 随着DeepSeek成为当前最热的现象级…...
小米AI眼镜官微上线,将与小米15 Ultra同台亮相,近屿智能用心培育 AI 人才
近日,小米眼镜官微已正式上线,认证主体为小米通讯技术有限公司。据悉,小米AI眼镜已获得入网许可,并计划提前至2月发布,与小米15 Ultra同台亮相。 此前,小米AI眼镜原定于2025年3月至4月发布。早在去年&#…...
Mac下使用brew安装go 以及遇到的问题
首先按照网上找到的命令进行安装 brew install go 打开终端输入go version,查看安装的go版本 go version 配置环境变量 查看go的环境变量配置: go env 事实上安装好后的go已经可以使用了。 在home/go下新建src/hello目录,在该目录中新建…...
在rtthread中,scons构建时,它是怎么知道是从rtconfig.h找宏定义,而不是从其他头文件找?
在rtthread源码中,每一个bsp芯片板级目录下都有一个 SConstruct scons构建脚本的入口, 在这里把rtthread tools/目录下的所有模块都添加到了系统路径中: 在tools下所有模块中,最重要的是building.py模块,在此脚本里面…...
Unity游戏(Assault空对地打击)开发(7) 爆炸效果
效果 准备 首先请手搓一个敌军基地。 然后添加一个火焰特效插件或者自建。 爆炸脚本编写 新建一个脚本命名为Explode。 无需挂载到对象上。 首先是全部代码。 using System.Collections; using System.Collections.Generic; using System.Linq; using TMPro; using UnityEngine…...
嵌入式面试题 C/C++常见面试题整理_7
一.什么函数不能声明为虚函数? 常见的不能声明为虚函数的有:普通函数(非成员函数):静态成员函数;内联成员函数;构造函数;友元函数。 1.为什么C不支持普通函数为虚函数?普通函数(非成员函数)只能被overload,不能被override,声明为虚函数也没有什么意思…...
