【算法杂货铺】分治


目录

🌈前言🌈

📁 快速排序

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

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

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

  📂 面试题 17.14. 最小K个数 - 力扣(LeetCode)

📁 归并排序

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

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

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

   📂 493. 翻转对 - 力扣(LeetCode)

📁 总结


🌈前言🌈

        欢迎收看本期【算法杂货铺】,本期内容将讲解分治思想,通过讲解快速排序和归并排序这两个排序来理解学习分治。

        本文旨在零基础学习,轻松搞懂,所以会不会讲解算法时间复杂度等验证,而是给出结论。当然,习题是会给出题解和思路的。

        此外,本文中所有代码都是C++实现,其他语言可以参考题解思路。

        为了照顾没有学习过快速排序的同学,本文会在讲解习题之前,讲解排序思路。

📁 快速排序

        快速排序的思想是选择一个基准元素key,以key为中间值,将数组分为两个区间,左区间的元素是严格小于key值,右区间元素严格大于key值。

        再将左区间和右区间排序,也是同样的思路,选出key值,划分左右区间,再进行排序。

        由上图可知,我们每次将数组排序,都将数组划分成两个区间,一共会执行logN层,每一层都会遍历n次,所以时间复杂度是O(NlogN)。

        快速排序的实现 : 三指针(数组分三块) +  随机选择基准元素。 

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

        在讲解快速排序之前,我们首先来学习一下,怎么将每一层的数组排序,即三指针(数组分三块)。

        这里我们使用三个指针(下标)来讲区间划分。

left : 表示0元素的末尾。

cur : 用来扫描数组。

right : 表示2的开头。

        扫描期间,cur要保证以下区间的稳定:

[0,left] : 都是0元素;

[left + 1 , cur - 1] : 都是1元素;

[cur , right - 1] : 是未扫描元素。

[right , size()-1] : 是2元素。

算法流程:

i. 初始化left = -1 , cur =0 , right = size() + 1;

ii. 循环扫描数组,cur < right时,cur就继续往后扫描数组。

        a. nums[cur] == 0时,就交换nums[left + 1] 和 nums[cur],再让left++,cur++。cur为什么可以++,因为left+1位置的元素,一定为0,或者1的。1的话,我们直接++即可;如果是0,就1种情况,cur == left + 1,所以是自己与自己交换,也可以直接++。

        b. nums[cur] == 1时,cur直接++。

        c. nums[cur] == 2时,交换nums[right - 1] 和 nums[cur],right--, 此时cur不能++,因为不能保证right-1位置的值一定是0 或者 1,所以需要接着判断交换后,i位置的值。

iii. cur >= right 表示扫描结束,此时总共有三个区间,分别代表0,1,2的区间。

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

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

        如果你理解颜色分类这道题,那么你以及掌握快速排序的大部分内容了,剩下的就是理解如果将区间划分,以及选择基准元素了。

        选择基准元素,最佳的策略是随机选择[left,right]范围内的任意元素,对应的是rand()函数,这样时间复杂度会达到最优解。

        将一层排完序后,再递归到左右区间进行排序。

class Solution {
public:vector<int> sortArray(vector<int>& nums) {srand(time(NULL));qsort(nums , 0 , nums.size()-1);return nums;}void qsort(vector<int>& nums,int l ,int r){if(l >= r)return;int key = GetRandom(nums,l,r);int left = l - 1 , right = r + 1;int cur = l;while(cur < right){if(nums[cur] < key)swap(nums[++left], nums[cur++]);else if(nums[cur] == key)cur++;else    swap(nums[--right],nums[cur]);}qsort(nums,l,left);qsort(nums,right,r);}int GetRandom(vector<int>& nums,int left,int right){return nums[rand()%(right - left + 1) + left];}
};

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

        通过快速排序,我们将区间划分成三块[0,left]  [left + 1 , right - 1]  [right , size()-1]。计算出每个区间元素个数,再到相应区间去找即可。

        算法就是 快速选择算法。

class Solution {
public:int findKthLargest(vector<int>& nums, int k) {srand(time(NULL));return qsort(nums , 0 , nums.size() - 1 , k);}int qsort(vector<int>& nums,int l , int r , int k){if(l == r)  return nums[l];int key = GetRandom(nums,l,r);int left = l - 1 , right = r + 1;int cur = l;while(cur < right){if(nums[cur] < key)swap(nums[++left],nums[cur++]);else if(nums[cur] == key)cur++;elseswap(nums[--right],nums[cur]);}int c = r - right + 1;int b = right - left - 1;if(c >= k )return qsort(nums,right,r,k);else if(b + c >= k)return key;else return qsort(nums,l,left,k-b-c);}int GetRandom(vector<int>& nums, int left , int right){return nums[rand()%(right - left + 1) + left];}
};

  📂 面试题 17.14. 最小K个数 - 力扣(LeetCode)

        依旧,是一道快速选择算法的题目,不过是从前面开始,并且是选k个数,那我们就将排好序的前k个数返回即可。顺序没有要求。

        顺序没有要求,意味着当k落在[left + 1, right - 1]这个区间的时候,直接返回即可。因为左边两个区间所有元素都是小于左区间的。

class Solution {
public:vector<int> smallestK(vector<int>& arr, int k) {srand(time(NULL));qsort(arr, 0 , arr.size()-1 , k);return {arr.begin() , arr.begin() + k};}void qsort(vector<int>& nums,int l,int r,int k){if(l >= r)return;int key = GetRandom(nums,l,r);int left = l - 1, right = r + 1;int cur = l;while(cur < right){if(nums[cur] < key)swap(nums[++left],nums[cur++]);else if(nums[cur] == key)cur++;else    swap(nums[--right] , nums[cur]);}int a = left - l + 1;int b = right - left - 1;if(a >= k)qsort(nums,l,left,k);else if(a + b >= k)return ;else    qsort(nums,right,r,k-a-b);}int GetRandom(vector<int>& nums,int left , int right){return nums[rand() % (right - left + 1) + left];}
};

📁 归并排序

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

        归并排序,非常充分的体现了分治思想,根据元素个数将数组划分成两个区间,直到只有1个元素,使整个数组的排序分为【左半部分排序】和【右半部分排序】。

        归并排序和快速排序有什么区别呢?归并排序是先将左区间排序,再将右区间排序,最后整体排序,是一种后序遍历的思路。快速排序是先将元素放在合适位置后,即先整体排序,再将左右区间排序,是一种前序遍历的思路。

        当然,还有就是归并排序是要有辅助数组的。

class Solution {
public:vector<int> temp;vector<int> sortArray(vector<int>& nums) {temp.resize(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 = (left + right) >>1;mergesort(nums,left,mid);mergesort(nums,mid+1 ,right);int cur1 = left,cur2 = mid+1;int i = left;while(cur1 <= mid && cur2 <= right){if(nums[cur1] <= nums[cur2]){temp[i++] = nums[cur1++];}else{temp[i++] = nums[cur2++];}}while(cur1 <= mid){temp[i++] = nums[cur1++];}while(cur2 <= right){temp[i++] = nums[cur2++];}for(int i= left ; i <= right ;i++)nums[i] = temp[i];}
};

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

        逆序对的概念就是有两个元素,后面的元素小于前一个元素。本期就是统计逆序对的个数,如果我们采用暴力解法,即从当前位置开始,往后遍历,找到比它小的就+1,肯定是不行的,时间复杂度为O(N^2)。

        用归并排序求逆序数是很经典的方法,主要是在归并排序的合并过程中统计处逆序的数量,也就是在合并两个有序序列的过程中,能快速求出逆序对的数量。

1. 为什么可以利用归并排序:

        将数组划分成两个,可以将逆序对产生的方式划分为3组:1. 全部从左数组来;2. 全部从右数组来;3. 左右数组各一个。根据排列组合的分类相加原理,三种情况下产生的逆序对总和,正好是总的逆序对的个数。

        这个思路正好匹配归并排序:1. 先排序做数组;2. 再排序有数组;3. 左右数组合并。

        因此,我们可以利用归并排序的过程,先求出左数组中逆序对的数量,再求出右数组中逆序对的个数,最后求出左右数组各一个逆序对的数量,三者相加即可。

2. 为什么要用归并排序

        在归并排序过程中,我们得到的是两个有序数组,可以利用数组的有序性,快速统计出逆序对的数量,而不是将所有情况枚举出来。

        有两个策略:

        1. 快速统计出某个数字前面有多少个数比它大;(升序)

        2. 快速统计出某个数组后面有多少个数比它小;(降序)

        这里的顺序是不能改变的,升序不能改为降序,可以理解为如果升序改为降序,可能会有重复的计算。

class Solution {
public:int temp[50010];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 = (left + right) >> 1;ret += mergesort(nums,left,mid);ret += mergesort(nums,mid+1,right);int cur1 = left , cur2 = mid+1;int i = left;while(cur1 <= mid &&cur2 <= right){if(nums[cur1] <= nums[cur2]){temp[i++] = nums[cur1++];}else{ret += mid - cur1 + 1;temp[i++] = nums[cur2++];}}while(cur1 <= mid){temp[i++] = nums[cur1++];}while(cur2 <= right){temp[i++] = nums[cur2++];}for(int i= left ; i <= right ;i++){nums[i] = temp[i];}return ret;}
};

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

各个数组和函数功能

 1. 创建两个全局数组:

vector<int> index : 记录下标

vector<int> ret : 记录结果

inde用来与原数组与对应位置的元素绑定,ret用来记录每个位置统计出来的逆序对的个数。

并创建index和ret的辅助数组。

2. countsamller()主函数:

a. 初始化两个全局数组,index初始化为数组的下标,ret初始化为0。

b. 为两个数组开辟大小为n的空间。

c. 调用mergesort()函数,并返回ret结果数组。

3. mergesort()函数

a. 定义递归出口:left>=right,直接返回。

b. 划分区间 [left , mid] 和 [mid + 1 , right]。

c. 统计左右区间逆序对数量

d. 统计左右区间逆序对数量。

class Solution {
public:vector<int> ret;vector<int> index;vector<int> indexTemp;vector<int> retTemp;vector<int> countSmaller(vector<int>& nums) {ret.resize(nums.size());index.resize(nums.size());indexTemp.resize(nums.size());retTemp.resize(nums.size());for(int i=0;i<nums.size();i++){ret[i] = 0;index[i] = i;}mergesort(nums, 0 , nums.size()-1 , ret);return ret;}void mergesort(vector<int>& nums,int left,int right,vector<int>& ret){if(left >= right)return;int mid = (left + right) >> 1;//处理左区间逆序对mergesort(nums,left,mid,ret);//处理右区间逆序对mergesort(nums,mid+1,right,ret);//处理一左一右int cur1 = left,cur2 = mid+1;int i = left;while(cur1 <= mid && cur2 <= right){if(nums[cur1] <= nums[cur2])                {retTemp[i] = nums[cur2];indexTemp[i++] = index[cur2++];}else{ret[index[cur1]] += right - cur2 +1;retTemp[i] = nums[cur1];indexTemp[i++] = index[cur1++];} }//处理剩余排序问题while(cur1 <= mid){retTemp[i] = nums[cur1];indexTemp[i++] = index[cur1++];}while(cur2 <= right){retTemp[i] = nums[cur2];indexTemp[i++] = index[cur2++];           }for(int i=left;i<=right;i++){nums[i] = retTemp[i];index[i] = indexTemp[i];}}
};

   📂 493. 翻转对 - 力扣(LeetCode)

        翻转对和逆序对的定义大同小异,你对许是前面的数要大于后面的数。而翻转数是前面的一个数要大于后面某个数的两倍。因此,我们可以采用归并排序来解决问题。

        与逆序对最大的不同在于,求逆序对时,可以在归并的时候统计逆序对的数量。而翻转对则需要提前统计。

class Solution {
public:int temp[50010];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;while(cur1 <= mid){//while(cur2 <= right && nums[cur2] >= nums[cur1] / 2.0) cur2++;while(cur2 <= right && nums[cur2] >= nums[cur1] / 2.0){cur2++;}if(cur2 > right){break;}ret += right - cur2 + 1;cur1++;}//合并有序数组cur1 = left,cur2 = mid+1;int i = left;while(cur1 <= mid && cur2 <= right){temp[i++] = nums[cur1] <= nums[cur2] ? nums[cur2++]:nums[cur1++];}while(cur1 <= mid){temp[i++] = nums[cur1++];}   while(cur2 <= right){temp[i++] = nums[cur2++];}for(int i= left ;i <= right ; i++){nums[i] = temp[i];}return ret;}
};

📁 总结

        以上,就是本期【算法杂货铺】的主要内容了,主要通过讲解快速排序和归并排序来学习分治思想。

        如果本期内容有帮助到你,欢迎点赞,关注,收藏。Thanks♪(・ω・)ノ

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

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

相关文章

突然发现!原来微信批量自动加好友这么简单!

你知道如何更好地管理和利用微信资源&#xff0c;实现客户拓展和沟通吗&#xff1f;下面就教大家一招&#xff0c;帮助大家实现统一管理多个微信号以及批量自动加好友。 想要统一管理多个微信号&#xff0c;不妨试试微信管理系统&#xff0c;不仅可以多个微信号同时登录&#…

数据分析概述、Conda环境搭建及JupyterLab的搭建

1. 数据分析职责概述 当今世界对信息技术的依赖程度在不断加深&#xff0c;每天都会有大量的数据产生&#xff0c;我们经常会感到数据越来越多&#xff0c;但是要从中发现有价值的信息却越来越难。这里所说的信息&#xff0c;可以理解为对数据集处理之后的结果&#xff0c;是从…

【Selenium(五)】

一、鼠标事件 from selenium import webdriver # 导入ActionChains类进行鼠标悬停操作 from selenium.webdriver.common.action_chains import ActionChains import time# 打开一个浏览器 # 法一、添加环境变量重启电脑 # 法二、填写浏览器驱动的绝对路径 driver webdriver.E…

vmare17 安装不可启动的iso镜像系统

由于要测试一个软件&#xff0c;要安装一个Windows11_InsiderPreview_Client_x64_zh-cn_26058.iso 于是在虚拟机里捣鼓一下。但是这个iso好像不能直接启动 这样就无法直接安装了&#xff0c;怎么办呢&#xff0c;可以先用个pe系统引导进去&#xff0c;再在PE系统里安装这个iso…

【免费】如何考取《鲸鸿动能广告初级优化师》认证(详细教程)

鲸鸿动能广告初级优化师认证考试PC网址 初级&#xff1a;鲸鸿动能广告初级优化师认证-华为开发者学堂 (huawei.com) 注&#xff1a;免费认证&#xff0c;里面包含免费的课程&#xff0c;浏览器用Edge。 文章目录 鲸鸿动能广告初级优化师认证考试网址 前言 一、备考流程 二…

软考 网络工程师 每日学习打卡 2024/3/21

学习内容 第8章 网络安全 本章主要讲解网络安全方面的基础知识和应用技术。针对考试应该掌握诸如数据加密、报文认 证、数字签名等基本理论&#xff0c;在此基础上深入理解网络安全协议的工作原理&#xff0c;并能够针对具体的 网络系统设计和实现简单的安全解决方案。 本章共有…

python云上水果超市的设计与实现flask-django-php-nodejs

伴随着我国社会的发展&#xff0c;人民生活质量日益提高。于是对云上水果超市进行规范而严格是十分有必要的&#xff0c;所以许许多多的信息管理系统应运而生。此时单靠人力应对这些事务就显得有些力不从心了。所以本论文将设计一套云上水果超市&#xff0c;帮助商家进行商品信…

如何为您的网站压缩图像

今天碰到一个客户反馈&#xff0c;他在hostease购买了虚拟主机&#xff0c;创建的WordPress站点图片比较多&#xff0c;后来访问网站&#xff0c;页面上大量的图片加载时间较长&#xff0c;咨询网站图像如何压缩。我们为用户提供网站图像压缩&#xff0c;用户很快完成了设置。在…

【Pt】新建项目时的设置

新建项目时需要在如下界面做一些设置。 一、模板与文件 模板通常选择“PBR - Metallic Roughness Alpha-blend” 文件可以选择fbx&#xff0c;abc&#xff0c;obj等格式的三维模型文件 二、项目设置 2.1 文件分辨率 指的是在软件中的预览效果&#xff0c;分辨率越高预览效果…

使用npm创建一个全局的cli命令,就像vue-cli一样

我们用过vue-cli等工具包&#xff0c;全局安装之后&#xff0c;我们可以直接使用vue create等命令&#xff0c;实际上能够这样使用的原因&#xff0c;就是使用了package.json里面的bin字段注册命令。接下来就以一个脚本文件为例子为大家演示一下bin是如何发挥作用的。 创建项目…

跳过mysql权限验证来修改密码-GPT纯享版

1.打开 MySQL 的配置文件&#xff0c;通常是 my.ini 或 my.cnf。 2.找到 [mysqld] 部分&#xff0c;如果没有则添加。 3.在 [mysqld] 部分中添加一行&#xff1a;skip-grant-tables&#xff0c;这个选项告诉 MySQL 服务器跳过权限验证&#xff0c;允许任何用户连接而不需要密码…

java数据结构与算法基础-----排序------堆排序

java数据结构与算法刷题目录&#xff08;剑指Offer、LeetCode、ACM&#xff09;-----主目录-----持续更新(进不去说明我没写完)&#xff1a;https://blog.csdn.net/grd_java/article/details/123063846 文章目录 堆排序是利用堆&#xff08;数据结构&#xff09;设计的排序算法…

基于SSM+Jsp+Mysql的KTV点歌系统

基于SSMJspMysql的KTV点歌系统 基于SSMJspMysql的KTV点歌系统的设计与实现 开发语言&#xff1a;Java框架&#xff1a;ssm技术&#xff1a;JSPJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工…

SRC中的一些信息收集姿势

前言 前前后后挖了四个月的EDUSRC&#xff0c;顺利从路人甲升到了网络安全专家&#xff0c;从提交的内容来看大部分还是以中低危为主&#xff0c;主打的就是弱口令和未授权。 在这过程中还是比较浮躁的&#xff0c;因此接下来的时间还是要好好沉淀一下自身的技术&#xff0c;学…

undo log

从这篇「执行一条 SQL 查询语句&#xff0c;期间发生了什么&#xff1f; (opens new window)」中&#xff0c;我们知道了一条查询语句经历的过程&#xff0c;这属于「读」一条记录的过程&#xff0c;如下图&#xff1a; 那么&#xff0c;执行一条 update 语句&#xff0c;期间发…

vue3 报错 require is not defined

问题 require is not defined 原因 vite 不支持require的用法&#xff0c; webpack是支持的 解决 方法一&#xff1a; 更改vite使用语法 vite官网 方法二 安装转换插件vite-plugin-require-transform 仓库地址 参考 关于Vite不能使用require问题 方法二Vite 踩坑 —— …

鸿蒙开发-UI-动画-页面间动画

鸿蒙开发-UI-组件导航-Navigation 鸿蒙开发-UI-组件导航-Tabs 鸿蒙开发-UI-图形-图片 鸿蒙开发-UI-图形-绘制几何图形 鸿蒙开发-UI-图形-绘制自定义图形 鸿蒙开发-UI-图形-页面内动画 鸿蒙开发-UI-图形-组件内转场动画 鸿蒙开发-UI-图形-弹簧曲线动画 文章目录 前言 一、放大缩…

【TD3思路及代码】【自用笔记】

1 组成&#xff08;Target Network Delayed Training&#xff09; Actor网络&#xff1a;这个网络负责根据当前的状态输出动作值。在训练过程中&#xff0c;Actor网络会不断地学习和优化&#xff0c;以输出更合适的动作。Critic网络&#xff1a;TD3中有两个Critic网络&#xff…

自然拼读-26个字母发音

自然拼读-26个字母发音 26个字母 Aa Bb Cc Dd Ee Ff Gg Hh Ii Jj Kk Ll Mm Nn Oo Pp Qq Rr Ss Tt Uu Vv Ww Xx Yy Zz 元音和辅音 辅音 元音 单词 元音 Aa Ee Li Oo Uu 另外&#xff1a;Yy是半元音 辅音 Bb Cc Dd Ff Gg Hh Jj Kk Ll Mm Nn Pp Qq Rr Ss Tt Vv Ww X…

利用二分法求方程在某个范围内的根

问题描述&#xff1a; 利用二分法求方程在&#xff08;-10,10&#xff09;的根。 方法&#xff1a;先求出两端点的中点&#xff0c;然后将中点带入方程中检查是否等于0&#xff0c;如果等于0说明找到了根&#xff0c;如果大于0&#xff0c;说明根在左半部分&#xff0c;将rig…