【算法杂货铺】分治


目录

🌈前言🌈

📁 快速排序

 📂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,一经查实,立即删除!

相关文章

力扣由浅至深 每日一题.10 最后一个单词的长度

日子都是崭新的&#xff0c;我们下一章见 ——24.3.21 最后一个单词的长度 给你一个字符串 s&#xff0c;由若干单词组成&#xff0c;单词前后…

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

你知道如何更好地管理和利用微信资源&#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…

vue中v-if和v-show的区别

手段&#xff1a;v-if是动态的向DOM树内添加或者删除DOM元素&#xff1b;v-show是通过设置DOM元素的display样式属性控制显隐&#xff1b;编译过程&#xff1a;v-if切换有一个局部编译/卸载的过程&#xff0c;切换过程中合适地销毁和重建内部的事件监听和子组件&#xff1b;v-s…

ARM IHI0069F GIC architecture specification (3)

1.2 术语 本手册中的架构描述使用与 Armv8 架构相同的术语。 有关此术语的更多信息&#xff0c;请参阅 Arm 架构参考手册 Armv8 A 部分的介绍&#xff0c;了解 Armv8-A 架构配置文件。 此外&#xff0c;在适当的情况下使用 AArch64 系统寄存器名称&#xff0c;而不是同时列出 A…

位运算算法

文章目录 预备知识判断字符是否唯⼀丢失的数字两整数之和只出现⼀次的数字只出现⼀次的数字II消失的两个数字 预备知识 给一个数n,确定它的二进制表示中的第x位是0还是1 (n>>(x-1)) & 1 1 true是1,false是0 将一个数n的二进制表示的第x位修改成1 n | (1<<(x-1…

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

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

实验4 词向量训练

必做题: 数据准备:数据集包含100个文件,每个文件里面有多个从维基百科上爬取的内容,每一条以字典形式保存,分为id,url,title,text四个字段,使用text字段的文本训练词向量。读取‘text’字段的文本,并使用jieba进行分词。使用Gensim工具训练词向量,训练方法为Skip-gr…

【链表】Leetcode 23. 合并 K 个升序链表【困难】

合并 K 个升序链表 给你一个链表数组&#xff0c;每个链表都已经按升序排列。 请你将所有链表合并到一个升序链表中&#xff0c;返回合并后的链表。 示例 1&#xff1a; 输入&#xff1a;lists [[1,4,5],[1,3,4],[2,6]] 输出&#xff1a;[1,1,2,3,4,4,5,6] 解释&#xff1…

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

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

元素和节点

节点操作和事件 今日目标&#xff1a; 节点的概念和分类 节点的创建&#xff0c;插入&#xff0c;替换&#xff0c;删除操作 事件三要素 常用事件类型 0.回顾 # 1. 获取元素的尺寸 offset一套 dom.offsetWidth: 内容 内边距 边框 dom.offsetHeightclient一套 dom.clie…

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

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

C语言经典例题(5) --- 交换数组、统计二进制、交换变量、菱形、字符串左旋

文章目录 1.交换数组2.统计二进制中1的个数3.交换两个变量(不创建临时变量)4.打印菱形5.字符串左旋 1.交换数组 将数组A中的内容和数组B中的内容进行交换。(数组大小一样) #include <stdio.h>void swap(int arr1[],int arr2[], int sz) {int tmp 0;for (int i 0;i &l…

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;允许任何用户连接而不需要密码…

OpenCV基于阈值的分割技术详细介绍

OpenCV 提供了基于阈值的分割技术&#xff0c;这是一种简单且常用的图像分割方法&#xff0c;其基本思想是根据像素的灰度值将图像分为不同的区域。下面详细介绍了 OpenCV 中基于阈值的分割技术&#xff1a; 全局阈值分割&#xff08;Global Thresholding&#xff09;&#xff…