【算法】利用分治思想解算法题:快排、归并、快速选择实战(C++)

1. 分治思想 介绍

分治法将问题划分成多个相互独立且相同或类似的子问题,然后递归地解决每个子问题,并将结果合并以得到原始问题的解。

分治思想通常包含以下三个步骤:

  1. 分解:将原始问题划分成多个规模较小、相互独立且类似的子问题。这个步骤可以通过递归方法实现。
  2. 解决:递归地解决每个子问题。当子问题足够小而可以直接求解时,使用简单的方法解决。
  3. 合并:将各个子问题的解合并,得到原始问题的解。

核心思想

是将一个复杂的问题分解成多个简单的子问题,通过递归地解决子问题,最终将子问题的解合并成原始问题的解

我们也遇到过一些,例如:归并排序、快速排序、二叉树的遍历等。

优缺点

优点在于能够有效地降低问题的复杂度,提高算法的效率。缺点在于需要额外的空间和时间来处理子问题的合并过程。


2. 分治思想 引入

75.颜色分类

在这里插入图片描述
思路

  • 题意分析:数组中只有三种元素0、1、2,要求按照012的顺序对数组进行原地排序
  • 解法一:排序
    • 我们想当然的可以想到用排序解决,这个解法也不用多说
  • 解法二:三指针划分数组
    • 数组中只有三种元素,我们利用三个指针
      在这里插入图片描述
  • 如上图所示

代码

void sortColors(vector<int>& nums) {// 三个指针划分区域// [0, left-1] : 0// [left, i] : 1// [i, right-1] : 未检测// [right, n-1] : 2int left = -1, i = 0, right = nums.size();while(i < right) // i,right相遇,全部检索完成{if(nums[i] == 0){swap(nums[++left], nums[i++]);}else if(nums[i] == 1){++i;}else{swap(nums[--right], nums[i]);}}
}

3. 分治 - 快排思想

912.排序数组-快排

思路

  • 解法:快速排序
    在这里插入图片描述
    • 如上图所示,快排本质就是通过一个key值将数组不断的分类排序,当所有子数组(左右区间)排序完毕后,向上返回,完成还原的操作
    • 细节注意:我们使用区间范围内的随机值作为key值,可以避免最坏情况,并均匀分割。

代码

class Solution {
public:// 获取随机数int getRandom(vector<int>& nums, int left, int right){int r = rand();return nums[r % (right - left) + left];}// 快排void qsort(vector<int>& nums, int l, int r){if(l >= r)  return;int left = l - 1, i = l, right = r + 1;int key = getRandom(nums, l, r);while(i < right){if(nums[i] < key)swap(nums[++left], nums[i++]);else if(nums[i] == key)i++;elseswap(nums[--right], nums[i]);}// [l, left] [left+1, right-1] [right,r]qsort(nums, l, left);qsort(nums, right, r);} vector<int> sortArray(vector<int>& nums) {// 快排 + 三数划分 + 随机数优化srand(time(NULL)); // 设置随机数种子qsort(nums, 0, nums.size() - 1);return nums;}
};

215.数组中的第K个最大元素

在这里插入图片描述

4. 分治 - 快速选择

215.数组中的第K个最大元素

思路

我们知道堆排序可以用于解决topk问题,时间复杂度为O(nlogn),这里在引入快速选择算法,一样可以用于解决topK问题,时间复杂度为O(n)

  • 解法:快速选择算法
    在这里插入图片描述
    • 如图所示,依然利用三数划分数组的思想:
    • 我们将数组划分为三部分,求出每一部分的长度
    • 根据k与数组长度关系,找到正确的区间,直到找到正确值

代码

class Solution {
public:// 获取随机数int getRandom(vector<int>& nums, int left, int right){ return nums[rand() % (right - left + 1) + left];}// 快排int qsort(vector<int>& nums, int l, int r, int k){if(l == r)  return nums[l];int left = l - 1, i = l, right = r + 1;int key = getRandom(nums, l, r);while(i < right){if(nums[i] < key) swap(nums[++left], nums[i++]);else if(nums[i] > key) swap(nums[--right], nums[i]);else i++;}// 找第k大的数// [l, left] [left + 1, right - 1] [right, r]// 区间元素个数: a b c// int a = left - l + 1;int b = (right - 1) - (left + 1) + 1, c = r - right + 1;if(c >= k) return qsort(nums, right, r, k); // 右区间 第k位else if(b + c >= k) return key;// else if(a >= k) qsort(nums, l, left, k-b-c);else return qsort(nums, l, left, k-b-c);}int findKthLargest(vector<int>& nums, int k) {// 快速选择算法srand(time(NULL));return qsort(nums, 0, nums.size() - 1, k);}
};

面试题17.14.最小K个数

在这里插入图片描述

思路

  • 题意分析:题目要求设计算法来返回数组中最小的k个数,我们可以使用堆排序,或者快速选择算法
  • 解法:排序 / 堆 / 快速选择
    • 对于解决类似的题,都可以有上述三种做法,但由于上一题题目要求O(n)的时间复杂度,所以选择快速选择算法
    • 这里我们依然以此思想解题
      在这里插入图片描述

代码

class Solution {
public:int getRandom(vector<int> &arr, int left, int right){return arr[rand() % (right - left + 1) + left];}void qsort(vector<int>& arr, int l, int r, int k){if(l >= r) return;int left = l - 1, i = l , right = r + 1;int key = getRandom(arr, l, r);while(i < right){if(arr[i] < key) swap(arr[++left], arr[i++]);else if(arr[i] > key) swap(arr[--right], arr[i]);else i++;}// [l, left] [left+1, right-1] [right, r]// 左区间长度: a | 中间区间长度: bint a = left - l + 1, b = right - left - 1;if(a > k) qsort(arr, l, left, k);else if(a + b >= k) return;else qsort(arr, right, r, k - a - b);}vector<int> smallestK(vector<int>& arr, int k) {// 三数划分 + 随机数作key + 快速选择srand(time(NULL)); // 设置随机数种子qsort(arr, 0, arr.size()-1, k);return vector<int>(arr.begin(), arr.begin() + k);}
};

5. 分治 - 归并思想

912.排序数组_归并

在这里插入图片描述

思路

在这里插入图片描述

在这里插入图片描述

  • 根据上图对比,我们知道归并与快排的区别,但两者都运用了分治的思想
  • 解法:归并
    1. 首先根据mid值递归划分数组
    2. 后向上返回,需要将两有序数组合并:
      • 通过两指针遍历数组,依次比较合并数组
      • 循环结束后将两数组中未合并的数添加
    3. 最后将合并后的数组添加到num中
    • 细节注意:我们使用全局数组tmp,用于节省递归多次创建的时间开胸啊

代码

class Solution {
public:vector<int> tmp; // 临时数组 当递归创建多次时,全局遍历节省时间开销void mergeSort(vector<int>& nums, int left, int right){if(left >= right) return;int mid = left + (right - left) / 2;// int mid = (left + right) >> 1;// 1. 数组划分mergeSort(nums, left, mid);mergeSort(nums, mid + 1, right);// 2. 合并数组int cur1 = left, cur2 = mid + 1, i = 0;while(cur1 <= mid && cur2 <= right)tmp[i++] = nums[cur1] <= nums[cur2] ? nums[cur1++] : nums[cur2++];// 2.5 处理未遍历完的数while(cur1 <= mid) tmp[i++] = nums[cur1++];while(cur2 <= right) tmp[i++] = nums[cur2++];// 3. 将元素写入到nums中for(int i = left; i <= right; ++i)nums[i] = tmp[i - left];}vector<int> sortArray(vector<int>& nums) {// 归并排序tmp.resize(nums.size());mergeSort(nums, 0, nums.size() - 1);return nums;}
};

LCR170.交易逆序对的总数

在这里插入图片描述

思路

  • 解法一:暴力枚举

    • 首先想到的当然是暴力解法,用两层for循环
    • 外层for循环遍历数组每次固定一个数,内层for循环遍历数组寻找比其小的数。
  • 解法二:归并
    在这里插入图片描述

  • 归并

    • 我们有两种策略,即排序时选择1. 升序 2. 降序
      在这里插入图片描述

    在这里插入图片描述

    • 我们选用策略1,即升序排序解题:
      1. 首先根据mid划分数组,递归左右部分并将逆序对个数加到ret中
      2. 对左右部分执行排序操作,当nums[cur1] > nums[cur2] 时,更新ret
      3. 将未合并的元素添加
      4. 最后合并数组

代码

class Solution {
public:vector<int> tmp;// 归并排序 + 计算逆序对个数int mergeSort(vector<int>& nums, int left, int right){if(left >= right) return 0; // 划分数组int mid = left + (right - left) / 2;int ret = 0; // 结果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;}   int reversePairs(vector<int>& record) {tmp.resize(record.size());return mergeSort(record, 0, record.size()-1);}
};

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

在这里插入图片描述

思路

  • 题意分析:题目要求找到数组nums中 “每一位其右侧小于该位的个数”,且将该个数存放到新数组count中

  • 解法一:暴力枚举

    • 很干脆,两层for循环,前固定数,后遍历从该数到数组末尾的元素,寻找小于自己的个数,统计到count中
  • 解法二:归并
    在这里插入图片描述

    • 根据题意,我们首先创建两个全局数组分别存放临时下标与临时元素,并用index数组保存nums中所有元素下标。
    1. 根据中点划分数组 + 向左右部分递归(找左右部分 满足条件的数)
    2. 通过两指针cur1、cur2遍历数组,进行比较(一左一右)
      • 随后执行上图操作
      • 当cur1元素 > cur2元素,此时cur2后所有元素都是小于自己的,更新结果,并记录cur1元素的下标与值
      • 当cur1元素 <= cur2元素,cur2继续向右移,找符合条件的值

代码

class Solution {
public:vector<int> ret; // 结果数组vector<int> index; // 记录元素移动前原始下标int tmpIndex[100001]; // 临时存放下标int tmpNums[100001]; // 临时存放元素void mergeSort(vector<int>& nums, int left, int right){if(left >= right) return;// 根据中点划分数组int mid = left + (right-left) / 2;// 递归排序+找数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];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(int j = left; j <= right; ++j){nums[j] = tmpNums[j-left]; // tmpNums是从0开始的,所以这里还原从0-leftindex[j] = tmpIndex[j-left];}}vector<int> countSmaller(vector<int>& nums) {// 哈希思想int n = nums.size();ret.resize(n);index.resize(n);// 初始化index数组 保存原始下标for(int i = 0; i < n-1; ++i)index[i] = i;mergeSort(nums, 0, n-1);return ret;}
};

493.翻转对

在这里插入图片描述

思路

  • 解法一:暴力枚举

    • 两层for循环,外层循环每次固定一位数,内层循环遍历数组判断是否nums[i] > nums[j] *
  • 解法二:归并

    1. 首先关于计算翻转对的操作:
      • 利用单调性,使用同向双指针
    2. 同前面的题一样,这里有两种策略:
      • 策略一:升序
        • 计算在当前元素前,有多少元素的两倍小于自己
      • 策略二:降序
        • 计算在当前元素前,有多少元素的一半大于自己

代码

class Solution {
public:vector<int> tmp;int mergeSort(vector<int>& nums, int left, int right) {if (left >= right) return 0;int ret = 0;// 先计算左右两侧的翻转对int mid = left + (right - left) / 2;ret += mergeSort(nums, left, mid);ret += mergeSort(nums, mid + 1, right);// 计算翻转对操作int cur1 = left, cur2 = mid + 1;while(cur1 <= mid){while(cur2 <= right && (nums[cur1] / 2.0 <= nums[cur2])) cur2++; // 找到符合条件的cur2位置,用*可能溢出if(cur2 > right) break;ret += right - cur2 + 1;++cur1;}// 具体排序// 降序判断cur1 = left, cur2 = mid + 1;int i = 0;while(cur1 <= mid && cur2 <= right)tmp[i++] = nums[cur1] <= nums[cur2] ? nums[cur2++] : nums[cur1++];// 排序剩余元素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 - left];return ret;}int reversePairs(vector<int>& nums) {tmp.resize(nums.size());return mergeSort(nums, 0, nums.size() - 1);}
};

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

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

相关文章

单片机大小端模式

单片机大小端模式 参考链接 单片机干货-什么是大小端_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1Ju4y1M7Tx/?spm_id_from333.337.search-card.all.click&vd_sourcee821a225c7ba4a7b85e5aa6d013ac92e 特此记录 anlog 2024年1月2日

在C++程序中调用被C编译器编译后的函数(使用extern “C“),举例详细说明

在C编程中&#xff0c;有时候我们需要调用由C编译器编译后生成的函数。然而&#xff0c;由于C和C在函数命名规则和调用约定上存在差异&#xff0c;直接在C代码中调用这些C函数会导致编译错误或运行时错误。为了解决这个问题&#xff0c;我们需要使用extern "C"来告诉…

response.setheader用法详解

response.setheader用法详解 大家好&#xff0c;我是免费搭建查券返利机器人赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01;今天&#xff0c;让我们深入探讨一项在Web开发中常用的技术——response.setheader的用…

文本到3D肖像最强生成方案!DiffusionGAN3D: 3D GANs和Diffusion先验强强联合!

本文介绍了一个新型框架DiffusionGAN3D&#xff0c;旨在改善文本引导的3D域适应和生成&#xff0c;以及解决现有方法在这些任务中存在的问题&#xff0c;如 inflexibility&#xff08;缺乏灵活性&#xff09;、instability&#xff08;不稳定性&#xff09;和low fidelity&…

Linux驱动学习—设备树及设备树下的platform总线

1、什么是设备树&#xff1f; 设备树是一种描述硬件资源的数据结构。他通过bootloader将硬件资源传给内核&#xff0c;使得内核和硬件资源 描述相对独立。 2、设备树的由来 2.1 平台总线的由来 要想了解为什么会有设备树&#xff0c;设备树是怎么来的&#xff0c;我们就要先…

计算机毕业论文内容参考|基于Apriori算法的门诊药物推荐系统的设计与实现

文章目录 摘要:前言相关技术与方法介绍系统分析系统设计系统实现系统测试与优化总结与展望摘要: 本文详细介绍了一种基于Apriori算法的门诊药物推荐系统的设计与实现。该系统利用Apriori算法挖掘患者就诊记录中的药物关联规则,为医生提供药物推荐,从而优化治疗方案。文章首…

2023春季李宏毅机器学习笔记 01 :正确认识 ChatGPT

资料 课程主页&#xff1a;https://speech.ee.ntu.edu.tw/~hylee/ml/2023-spring.phpGithub&#xff1a;https://github.com/Fafa-DL/Lhy_Machine_LearningB站课程&#xff1a;https://space.bilibili.com/253734135/channel/collectiondetail?sid2014800 一、对Chatgpt的误解…

CISSP 第5章 保护资产的安全

1、资产识别和分类 1.1 敏感数据 1.1.1 定义 敏感数据是任何非公开或非机密的信息&#xff0c;包括机密的、专有的、受保护的或因其对组织的价值或按照现有的法律和法规而需要组织保护的任何其他类型的数据。 1.1.2 个人身份信息PII 个人身份信息&#xff08;PII&#xff09…

python旅游大数据分析可视化大屏 游客分析+商家分析+舆情分析 计算机毕业设计(附源码)Flask框架✅

毕业设计&#xff1a;2023-2024年计算机专业毕业设计选题汇总&#xff08;建议收藏&#xff09; 毕业设计&#xff1a;2023-2024年最新最全计算机专业毕设选题推荐汇总 &#x1f345;感兴趣的可以先收藏起来&#xff0c;点赞、关注不迷路&#xff0c;大家在毕设选题&#xff…

[GKCTF 2020]ez三剑客-eztypecho

[GKCTF 2020]ez三剑客-eztypecho 考点&#xff1a;Typecho反序列化漏洞 打开题目&#xff0c;发现是typecho的CMS 尝试跟着创建数据库发现不行&#xff0c;那么就搜搜此版本的相关信息发现存在反序列化漏洞 参考文章 跟着该文章分析来&#xff0c;首先找到install.php&#xf…

Ubuntu20.04安装suiteCRM

两篇有用的文章 在ubuntu16.04上安装suitecrm_suitecrm ubuntu-CSDN博客 SuiteCRM搭建安装&#xff08;apachemsyqlphp&#xff09;_suitecrm 宝塔安装-CSDN博客 对照着一步步操作就可以了

基于多反应堆的高并发服务器【C/C++/Reactor】(中)处理任务队列中的任务 添加 删除 修改

&#xff08;1&#xff09;EventLoop启动 EventLoop初始化和启动 // 启动反应堆模型 int eventLoopRun(struct EventLoop* evLoop) {assert(evLoop ! NULL);// 取出事件分发和检测模型struct Dispatcher* dispatcher evLoop->dispatcher;// 比较线程ID是否正常if(evLoop-&…

grep -A -B -C 输出匹配行及相邻行

grep -A -B -C 输出匹配行及相邻行 grep --help 摘抄&#x1f447; 文件控制&#xff1a; -B, --before-context数值 打印前面 <数值> 行上下文-A, --after-context数值 打印后面 <数值> 行上下文-C, --context数值 打印前后 <数值> 行上下文 文件控制&#…

python小工具之弱密码检测工具

一、引用的python模块 Crypto&#xff1a; Python中一个强大的加密模块&#xff0c;提供了许多常见的加密算法和工具。它建立在pyc.ypodome或pyc.ypto等底层加密库之上&#xff0c;为Python程序员提供了简单易用的API&#xff0c;使其可以轻松地实现各种加密功能。 commands…

STM32MP157D-DK1 Qt程序交叉编译与运行测试

上篇文章介绍了STM32MP157D-DK1开发板Qt镜像的构建&#xff0c;通过在Ubuntu中重新编译带有Qt功能的系统来实现。 本篇在上篇的基础上&#xff0c;继续搭建Qt的交叉编译环境&#xff0c;实现Qt程序在Ubuntu中编译&#xff0c;在STM32MP157板子中运行。 1 编译安装SDK 在上篇…

阿里云和腾讯云服务器系统盘40G或50G空间够用吗?

云服务器系统盘40G或50G空间够用吗&#xff1f;够用&#xff0c;操作系统一般占用几个GB的存储空间&#xff0c;尤其是Linux操作系统占用空间容量更小&#xff0c;阿里云和腾讯云服务器系统盘默认提供的40GB高效云盘或50G通用型SSD云硬盘&#xff0c;阿腾云atengyun.com分享是否…

写你的第一个Vue程序

Vue.js渐进式JavaScript框架&#xff0c;Vue是一款用于构建用户界面的JavaScript框架。它基于标准HTML、CSS和JavaScript构建&#xff0c;并提供了一套声明式的、组件化的编程模型&#xff0c;帮助开发者高效地开发用户界面。 写你的第一个Vue程序 <!DOCTYPE html> <…

【机器学习前置知识】多项式分布

多项式分布是二项式分布的推广。 在二项分布这篇文章中我们曾以抛硬币举例&#xff1a;在一次抛硬币实验中结果只有两种情况&#xff0c;正面或反面向上&#xff1b;在 n n n 次抛硬币实验中&#xff0c;正面向上出现 k k k 次的有 C n k n ! k ! ( n − k ) ! C_{n}^k{n!…

计算机组成原理-总线的性能指标

文章目录 总览总线周期 总线时钟周期 总线工作频率 总线时钟频率总线宽度 总线带宽例题串行总线和并行总线的速度&#xff08;带宽&#xff09;比较总线复用 信号线数总结 总览 总线周期 总线时钟周期 总线工作频率 总线时钟频率 一个总线周期就是指利用总线传输一组数据需要的…

详解进制之间的转换

目录 一、十进制转换 1、十进制转换为二进制 2、十进制转换为八进制 3、十进制转换为十六进制 二、二进制转换 1、二进制转换为八进制 2、二进制转换成十进制 3、二进制转换为十六进制 三、八进制转换 1、八进制转换成二进制 2、八进制转换成十进制 3、八进制转换成…