【优选算法】分治 {三分快排:三指针优化,随机选key,快速选择算法;归并排序:统计数组中的逆序对,统计数组中的翻转对;相关编程题解析}

一、经验总结

1.1 三分快排

优化一:三指针优化

之前学习的快速排序无法妥善处理相等或重复序列的排序问题(有序且三数取中无效),使快速排序的效率无法达到最优。

为了解决重复序列的问题,我们将原先的双指针法(前后指针)优化为三指针,将数组划分成三块:

  • [0, left]:< key
  • [left+1, right-1]:==key
  • [riight, n-1]:> key
  • 其中left标记<key区间的最右侧;i负责从左向右遍历数组;right标记>key区间的最左侧;

之后,再利用分治思想将<key和>key的部分进行排序即可,所有==key的部分已经移动到了最终的位置上。完美的解决了重复序列的问题。

举个极端一点的例子,对于全体重复的序列,原先需要partition n次,每次都要将区间遍历一遍是一个典型的复杂度为O(N^2)的算法。现在,仅需要partition一次就可以将所有数字归入==key的区间,不再有<key和>key的部分排序结束,复杂度降为O(N)。

优化二:随机选key

之前我们使用的是取最左(右)值为key、三数取中为key。实际上随机取key可以使数组划分的更为均匀,每个区间都是等概率划分的。使快速排序的时间复杂度更接近于O(NlogN)。

三分快排的应用:快速选择算法

快速选择算法是解决Topk问题的最优方案,之前学习过的利用堆解决Topk问题时间复杂度为O(NlogK),已经相当高效了。但是快速选择算法可以将时间复杂度优化为O(N)。

快速选择算法的原理是基于三分快排的,但并不需要将数组完全排序,而是将数组划分为三块以后,将三块区间内元素的个数与k比较,再进行递归分割,直到将最小(最大)的前k个数全部移动到数组前面(后面)。

Topk问题又分前k小(大)、第k小(大)。前k小只需要将最小的前k个数全部移动到数组前面即可,<key区间内的元素个数只要==k就可以返回。而第k小不仅要移动最小的前k个数,还必须找到第k个,即第k个数必须刚好落在==key的区间内才能返回。


1.2 归并排序

利用归并排序统计数组中的逆序对

所谓逆序对是指前大后小的一对数,利用归并排序统计逆序对可以将暴力解法的时间复杂度O(N^2),优化为O(NlogN)。算法思路如下:

  1. 将数组从中间一分为二,先统计左右区间内的逆序对,并进行排序。
  2. 然后再归并左右区间的过程中,统计一左一右跨两个区间的逆序对,有两个策略可供选择
    1. 升序排序:以右区间中的元素cur2为基点,在左区间中找大于cur2的元素cur1,因为是升序所以左区间之后的元素都大。
    2. 降序排序:以左区间中的元素cur1为基点,在右区间中找小于cur1的元素cur2,因为是降序所以右区间之后的元素都小
  3. 颠来倒去其实都是在cur1 > cur2的时候,统计逆序对的数量,只是基点不同:先左后右是降序,先右后左是升序
  4. 逆序对的判定规则与左右区间归并的比较规则相同,所以可以在左右区间归并的过程中顺道统计逆序对的个数。

利用归并排序统计数组中的翻转对

不同于逆序对,翻转对要求前一个数大于后一个数的两倍。翻转对的判定规则与左右区间归并的比较规则不同,也就不能顺道了。但是翻转对的判定与统计仍然可以利用归并排序的分治和左右区间有序的条件,只是需要在左右区间归并之前,先一步进行一左一右的翻转对统计即可。

在统计一左一右跨两个区间的翻转对时,算法规律和逆序对相同:在cur1/2 > cur2(乘法改除法防溢出)的时候,统计翻转对的数量。先左后右是降序,先右后左是升序。算法还可以使用同向双指针进行优化,只需要将左右两个区间遍历一遍O(N),就可以完成统计。不会影响整体归并排序的复杂度O(NlogN)。


二、相关编程题

2.1 三分快排

2.1.1 颜色分类

题目链接

75. 颜色分类 - 力扣(LeetCode)

题目描述

在这里插入图片描述

算法原理

在这里插入图片描述

编写代码

class Solution {
public:void sortColors(vector<int>& nums) {int n = nums.size();int left = -1, i = 0, right = n;while (i < right) {if (nums[i] == 0) {if (++left != i)swap(nums[left], nums[i]);++i;} else if (nums[i] == 1) {++i;} else if (nums[i] == 2 && --right != i) {swap(nums[i], nums[right]);}}}
};

2.1.2 优化快速排序

题目链接

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

题目描述

在这里插入图片描述

算法原理

在这里插入图片描述

编写代码

class Solution {
public:vector<int> sortArray(vector<int>& nums) {srand(time(nullptr));QuickSort(nums, 0, nums.size()); //注意区间是左闭右开return nums;}void QuickSort(vector<int>& nums, int begin, int end){if(end - begin < 2) return;int key = nums[rand()%(end-begin)+begin]; //随机取keyint left = begin-1, i = begin, right = end;while(i < right){if(nums[i] < key)   swap(nums[++left], nums[i++]);else if(nums[i] == key) ++i;else swap(nums[--right], nums[i]);}//left和right都是闭端点QuickSort(nums, begin, left+1); //left做end需要+1(右开)QuickSort(nums, right, end); //right做begin不需要+1(左闭)}
};

2.1.3 数组中的第k个最大元素

题目链接

215. 数组中的第K个最大元素 - 力扣(LeetCode)

题目描述

在这里插入图片描述

算法原理

在这里插入图片描述

编写代码

//快速选择算法 O(N)
class Solution {
public:int findKthLargest(vector<int>& nums, int k) {srand(time(nullptr));return QuickSelect(nums, 0, nums.size()-1, k); //注意区间是左闭右闭}int QuickSelect(vector<int>& nums, int begin, int end, int k){// 当区间内只有一个元素时,直接返回这个元素if(begin == end) return nums[begin];// 随机选keyint key = nums[rand()%(end-begin+1)+begin]; // 将区间内的元素划分成三块int left = begin-1, i = begin, right = end+1;while(i < right){if(nums[i] < key) swap(nums[++left], nums[i++]);else if(nums[i] == key) ++i;else swap(nums[--right], nums[i]);}//核心逻辑if(end-right+1 >= k) //c>=kreturn QuickSelect(nums, right, end, k); else if(end-left >= k) //b+c>=kreturn key;else    return QuickSelect(nums, begin, left, k-(end-left)); //找k-b-c大的数}
};//堆算法 O(NlogK)
class Solution {
public:Kint findKthLargest(vector<int>& nums, int k) {vector<int> leastHeap(k);for(int i = 0; i < k; ++i){leastHeap[i] = nums[i];}for(int i = k-2/2; i >= 0; --i){AdjustDown(leastHeap, i);}for(int i = k; i < nums.size(); ++i){if(nums[i] > leastHeap[0]) {leastHeap[0] = nums[i];AdjustDown(leastHeap, 0);}}return leastHeap[0];}void AdjustDown(vector<int>& nums, int root) {int parent = root;int child = parent * 2 + 1;int n = nums.size();while (child < n) {if (child + 1 < n && nums[child + 1] < nums[child]) {++child;}if (nums[child] < nums[parent]) {swap(nums[child], nums[parent]);parent = child;child = parent * 2 + 1;} else {break;}}}
};

2.1.4 最小的k个数

题目链接

LCR 159. 库存管理 III - 力扣(LeetCode)

题目描述

在这里插入图片描述

算法原理

在这里插入图片描述

编写代码

class Solution {
public:vector<int> inventoryManagement(vector<int>& stock, int cnt) {srand(time(nullptr));if(cnt > 0)QuickSelect(stock, 0, stock.size()-1, cnt);return vector<int> (stock.begin(), stock.begin()+cnt);}void QuickSelect(vector<int>& nums, int begin, int end, int k){// 当区间内只有一个元素时,直接返回if(begin == end) return;// 随机选keyint key = nums[rand()%(end-begin+1)+begin]; // 将区间内的元素划分成三块int left = begin-1, i = begin, right = end+1;while(i < right){if(nums[i] < key) swap(nums[++left], nums[i++]);else if(nums[i] == key) ++i;else swap(nums[--right], nums[i]);}//核心逻辑if(left-begin+1 > k) //a>kQuickSelect(nums, begin, left, k); else if(right-begin >= k) //a+b>=kreturn;else    QuickSelect(nums, right, end, k-(right-begin)); }
};

2.2 归并排序

2.2.1 归并排序

题目链接

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

题目描述

在这里插入图片描述

算法原理

在这里插入图片描述

编写代码

class Solution {
public:vector<int> sortArray(vector<int>& nums) {vector<int> tmp(nums.size()); //辅助数组在递归外创建效率更高MergeSort(nums, 0, nums.size(), tmp);return nums;}void MergeSort(vector<int>& nums, int begin, int end, vector<int>& tmp){if(end-begin < 2) return;int begin1 = begin;int end1 = begin+(end-begin)/2;int begin2 = end1;int end2 = end;MergeSort(nums, begin1, end1, tmp);MergeSort(nums, begin2, end2, tmp);//归并左右两顺序区间int i = begin;while(begin1 < end1 && begin2 < end2){if(nums[begin1] <= nums[begin2]){tmp[i++] = nums[begin1++];}else{tmp[i++] = nums[begin2++];}}while(begin1 < end1){tmp[i++] = nums[begin1++];}while(begin2 < end2){tmp[i++] = nums[begin2++];}for(int i = begin; i < end; ++i){nums[i] = tmp[i];}}
};

2.2.2 数组中的逆序对

题目链接

LCR 170. 交易逆序对的总数 - 力扣(LeetCode)

题目描述

在这里插入图片描述

算法原理

在这里插入图片描述

编写代码

class Solution {
public:int reversePairs(vector<int>& record) {vector<int> tmp(record.size());return MergeSort(record, 0, record.size(), tmp);}int MergeSort(vector<int>& nums, int begin, int end, vector<int>& tmp){//如果区间内的元素个数小于2,返回0个逆序对if(end-begin < 2) return 0;//将区间从中间划分成左右两个区间int begin1 = begin;int end1 = begin+(end-begin)/2;int begin2 = end1;int end2 = end;int cnt = 0;//左区间的个数+排序;右区间的个数+排序cnt += MergeSort(nums, begin1, end1, tmp);cnt += MergeSort(nums, begin2, end2, tmp);//一左一右的个数+归并排序//策略一:以cur2为基点,在之前找大int i = begin;while(begin1 < end1 && begin2 < end2){if(nums[begin1] <= nums[begin2]){tmp[i++] = nums[begin1++];}else{cnt += end1-begin1;tmp[i++] = nums[begin2++];}}//策略二:以cur1为基点在之后找小// while(begin1 < end1 && begin2 < end2)// {//     if(nums[begin1] > nums[begin2])//     {//         cnt += end2 - begin2;//         tmp[i++] = nums[begin1++];//     }//     else//     {//         tmp[i++] = nums[begin2++];//     }// }while(begin1 < end1){tmp[i++] = nums[begin1++];}while(begin2 < end2){tmp[i++] = nums[begin2++];}//将归并排序好的区间元素拷贝回原数组for(int i = begin; i < end; ++i){nums[i] = tmp[i];}return cnt; //返回的就是区间内的逆序对总数}
};

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

题目链接

315. 计算右侧小于当前元素的个数 - 力扣(LeetCode)

题目描述

在这里插入图片描述

算法原理

在这里插入图片描述

编写代码

class Solution {vector<int> tmp1, tmp2;
public:vector<int> countSmaller(vector<int>& nums) {tmp1.resize(nums.size()); //用于归并排序numstmp2.resize(nums.size()); //用于归并index,并不是排序,只是执行和nums同样的操作vector<int> ret(nums.size(), 0); //结果数组vector<int> index(nums.size()); //用于映射每个元素的原始下标for(int i = 0; i < nums.size(); ++i){index[i] = i;}MergeSort(nums, 0, nums.size(), ret, index);return ret;}void MergeSort(vector<int>& nums, int begin, int end, vector<int>& ret, vector<int>& index){if(end - begin < 2) return;//将数组从中间分成两个区间int begin1 = begin;int end1 = begin1+(end-begin)/2;int begin2 = end1;int end2 = end;//先分别处理左右区间内的个数MergeSort(nums, begin1, end1, ret, index);MergeSort(nums, begin2, end2, ret, index);//再处理一左一右的个数int i = begin;while(begin1 < end1 && begin2 < end2){if(nums[begin1] > nums[begin2]) //降序排序{ret[index[begin1]] += end2-begin2; //注意:1.获取元素的原始下标 2.+=可能在左右区间中已经统计过了tmp1[i] = nums[begin1];tmp2[i++] = index[begin1++]; //nums数组中的元素移动到哪,index数组中的原始下标就移动到哪}else{tmp1[i] = nums[begin2];tmp2[i++] = index[begin2++];}}while(begin1 < end1){tmp1[i] = nums[begin1];tmp2[i++] = index[begin1++];}while(begin2 < end2){tmp1[i] = nums[begin2];tmp2[i++] = index[begin2++];}for(int i = begin; i < end; ++i){nums[i] = tmp1[i];index[i] = tmp2[i];}}
};

2.2.4 翻转对

题目链接

493. 翻转对 - 力扣(LeetCode)

题目描述

在这里插入图片描述

算法原理

在这里插入图片描述

编写代码

class Solution {vector<int> tmp;
public:int reversePairs(vector<int>& nums) {int n = nums.size();tmp.resize(n);return MergeSort(nums, 0, n);}   int MergeSort(vector<int>& nums, int begin, int end){if(end-begin < 2) return 0;//将数组从中间划分成左右两个区间int begin1 = begin;int end1 = begin1+(end-begin)/2;int begin2 = end1;int end2 = end;int cnt = 0;//分别去左右区间统计翻转对并进行排序cnt += MergeSort(nums, begin1, end1);cnt += MergeSort(nums, begin2, end2);//统计一左一右跨两个区间的翻转对int cur1 = begin1, cur2 = begin2;while(cur1 < end1 && cur2 < end2){//策略一:在cur1后面找*2都比它小的数if(nums[cur1]/2.0 > nums[cur2]) //乘法改除法,防溢出{cnt += end2-cur2; //由于是降序,所以之后的都小++cur1;}else++cur2;//策略二:在cur2前面找/2都比它大的数// if(nums[cur1]/2.0 > nums[cur2])// {//     cnt += end1-cur1; //由于是升序,所以之后的都大//     ++cur2;// }// else//     ++cur1;}int i = begin;while(begin1 < end1 && begin2 < end2){//降序排序if(nums[begin1] >= nums[begin2]){tmp[i++] = nums[begin1++];}else{tmp[i++] = nums[begin2++];}//升序排序// if(nums[begin1] <= nums[begin2])// {//     tmp[i++] = nums[begin1++];// }// else// {//     tmp[i++] = nums[begin2++];// }}while(begin1 < end1){tmp[i++] = nums[begin1++];}while(begin2 < end2){tmp[i++] = nums[begin2++];}for(int i = begin; i < end; ++i){nums[i] = tmp[i];}return cnt;}
};

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

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

相关文章

云计算-无服务器计算与AWS Lambda (Serverless Computing with AWS Lambda)

AWS Lambda 无服务器计算与AWS Lambda AWS Lambda支持无服务器计算&#xff0c;不需要任何预配置和管理&#xff0c;同时还能最大限度地降低成本。我们将看到如何创建一个简单的Lambda函数&#xff0c;以及如何将其与AWS事件映射。在现实生活中&#xff0c;任何托管在线的应用…

每天学点小知识:图床搭建 + CDN简介

前言&#xff1a; 本章内容帮你解决&#xff0c;本地图片不能分享到网上的问题。需要工具github JSDelivr 知识点 Q&#xff1a;什么是JSDelivr&#xff1f; JSDelivr是一个免费且公开的内容分发网络&#xff08;CDN&#xff09;&#xff0c;专门用于加速开源项目和静态网站…

构建php环境、安装、依赖、nginx配置、ab压力测试命令、添加php-fpm为系统服务

目录 php简介 官网php安装包 选择下载稳定版本 &#xff08;建议使用此版本&#xff0c;文章以此版本为例&#xff09; 安装php解析环境 准备工作 安装依赖 zlib-devel 和 libxml2-devel包。 安装扩展工具库 安装 libmcrypt 安装 mhash 安装mcrypt 安装php 选项含…

2024年软件设计师备考复习资料(应用技术)

应用设计&#xff0c;考试时间为120分钟&#xff1b;总共需做5道题&#xff0c;满分75分&#xff08;每题15分&#xff09;。前4题为必答题&#xff0c;最后2题为要求选答一题&#xff08;C或Java&#xff09;&#xff0c;45及格 目录 1. 数据流图&#xff08;需求分析&#…

Python使用Modbus RTU发送数据的技术性指南

目录 一、引言 二、Modbus RTU协议简介 三、Pymodbus库介绍 四、环境准备 五、编写Modbus RTU客户端代码 六、案例分析 七、注意事项与调试技巧 八、扩展功能与应用 九、性能优化与安全性考虑 十、总结 一、引言 在工业自动化领域中&#xff0c;Modbus协议因其开放性…

opencascade AIS_Circle AIS_ColoredDrawer AIS_CameraFrustum 源码学习 圆

类AIS_Circle 构造圆形基准面&#xff0c;用于构建复合形状。 AIS_Circle() [1/2] AIS_Circle::AIS_Circle ( const Handle< Geom_Circle > & aCircle ) 初始化用于构造 AIS 圆形基准面的算法&#xff0c;并初始化圆形 aCircle。 AIS_Circle() [2/2] AIS_Circ…

数据库系统概论(个人笔记)(第三部分)

数据库系统概论&#xff08;个人笔记&#xff09; 文章目录 数据库系统概论&#xff08;个人笔记&#xff09;3、SQL介绍3.1 SQL查询语言概述3.2 SQL数据定义3.3 SQL查询的基本查询结构3.4 其他基本操作3.5 设置操作3.6 空值3.7 聚合函数3.8 嵌套子查询3.9 数据库的修改 3、SQL…

LES物流执行系统,在离散制造行业有那些作用和价值?

离散制造企业往往面临的是多品种、小批量的非标订单生产&#xff0c;传统推动式物流系统已经无法应对计划变化滞后&#xff0c;各车间、工序之间难以衔接等情况&#xff0c;特别是密集劳动力的电子行业&#xff0c;非标产品 SKU 种类繁多&#xff0c;物料配送复杂&#xff0c;对…

【Flowable 7】学习笔记 01 - 初始化数据库表创建流程(源码)

文章目录 前言版本说明配置1、引擎配置初始化2、SQL 执行创建表2.0、创建表概览&#xff08;创建表数目&#xff1a;38&#xff09;2.1、基础组件表创建&#xff08;以 common 组件为例&#xff09;2.2、changelog 组件表创建&#xff08;基于 liquibase&#xff09;2.3、Engin…

探索机器人智能设备:开启智慧生活新篇章

机器人智能设备作为科技创新的代表&#xff0c;正以其独特的魅力吸引着越来越多的关注。它们不仅具备高度的智能化和自主化能力&#xff0c;还能在各种场景下发挥出强大的功能。 机器人智能设备的张总说&#xff1a;在智能家居领域&#xff0c;机器人智能设备可以帮助我们实现家…

计算机网络导论

网络结构的演变 网状结构 最开始的网络&#xff0c;主机之间都是两两相连 好处 这样连接&#xff0c;好处是安全性比较高&#xff08;A与B之间的连线断了&#xff0c;可以绕一下C&#xff09;&#xff1b; 另外通信不需要互相等待&#xff08;没有中间交换设备&#xff0c;所…

头文件大小写引发的报错

jenkins下打包编译报错如下&#xff0c;提示编译zynqCan.c时找不到“syscfgpll/sysCfgpll.h”文件。 但IDE下编译是没有报错也没有警告的&#xff0c;工程中也存在文件“syscfgpll/sysCfgPll.h”。 仔细观察发现&#xff0c;报错说的是找不到头文件“syscfgpll/sysCfgpll.h”…

09Django项目--用户管理系统--删

对应视频链接点击直达 09Django项目--用户管理系统--删 对应视频链接点击直达删a&#xff0c;本质b&#xff0c;删除 页面相关a&#xff0c;index页面新增操作按钮b&#xff0c;ajax删除和提示c&#xff0c;完整版本 OVER&#xff0c;不会有人不会吧不会的加Q1394006513结语 一…

视频监控汇聚平台LntonCVS视频安防监控平台城市积水智能监测应用方案

根据中央气象台的最新预报&#xff0c;我国南方地区即将面临一轮强烈的降雨天气。这场降雨预计将会给部分地区带来大到暴雨的降水量&#xff0c;甚至在某些局部地区可能出现极端的大暴雨情况。与此同时&#xff0c;华北、黄淮以及东北地区也可能会遭受雷雨天气的干扰&#xff0…

Python | Leetcode Python题解之第118题杨辉三角

题目&#xff1a; 题解&#xff1a; class Solution:def generate(self, numRows: int) -> List[List[int]]:ret list()for i in range(numRows):row list()for j in range(0, i 1):if j 0 or j i:row.append(1)else:row.append(ret[i - 1][j] ret[i - 1][j - 1])ret…

电商api接口进行数据采集获取淘宝/天猫/京东/抖音多平台商品价格

在电商运营中&#xff0c;从品牌角度来看&#xff0c;品牌方通过电商数据采集API接口进行数据采集&#xff0c;获取多渠道商品价格信息的这一行为&#xff0c;能为品牌方带来诸多好处&#xff1a; 及时准确&#xff1a;API接口能为品牌提供实时数据&#xff0c;这意味着企业可…

RT-DETR算法改进【NO.1】借鉴CVPR2024中的StarNet网络StarBlock改进算法

前 言 YOLO算法改进的路有点拥挤,尝试选择其他的baseline作为算法研究,可能会更加好发一些文章。后面将陆续介绍RT-DETR算法改进的方法思路。 很多朋友问改进如何选择是最佳的,下面我就根据个人多年的写作发文章以及指导发文章的经验来看,按照优先顺序进行排序讲解…

德克萨斯大学奥斯汀分校自然语言处理硕士课程汉化版(第二周) - 多类别分类和神经网络

多类别分类和神经网络 1. 多类别分类2. 多类别感知机和多类别逻辑回归3. 多类别分类的场景4. 分类公平性5. 神经网络6. 神经网络可视化7. 神经网络的前向传播和反向传播8. 神经网络的训练与优化 1. 多类别分类 分类是一个预测建模问题&#xff0c;它涉及到在给定输入的情况下…

第十二周 5.21面向对象的三大特性(封装、继承、多态)(二)

三、多态 1.理解: (1)多态:父类型的引用存储不同子类型的对象 父类类名 引用名 new 子类类名(); 引用 对象 父类型 子类型 …

数字孪生技术助力智慧园区建设

随着城市化进程的加速和科技创新的推动&#xff0c;城市面临着诸多挑战和机遇。如何提升城市的竞争力和可持续性&#xff0c;是一个亟待解决的问题。在这个背景下&#xff0c;智慧园区作为一种新型的城市发展模式&#xff0c;引起了越来越多的关注和探索。 什么是智慧园区&…