【算法】利用分治思想解算法题:快排、归并、快速选择实战(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日

文本到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的误解…

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…

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 在上篇…

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

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

C#线程基础(线程启动和停止)

目录 一、关于线程 二、示例 三、生成效果 一、关于线程 在使用多线程前要先引用命名空间System.Threading&#xff0c;引用命名空间后就可以在需要的地方方便地创建并使用线程。 创建线程对象的构造方法中使用了ThreadStart()委托&#xff0c;当线程开始执行时&#xff0c…

LeetCode第32题 : 最长有效括号

题目介绍 给你一个只包含 ( 和 ) 的字符串&#xff0c;找出最长有效&#xff08;格式正确且连续&#xff09;括号子串的长度。 示例 1&#xff1a; 输入&#xff1a;s "(()" 输出&#xff1a;2 解释&#xff1a;最长有效括号子串是 "()" 示例 2&#xf…

springCould中的Hystrix【上】-从小白开始【7】

目录 1.简单介绍❤️❤️❤️ 2.主要功能 ❤️❤️❤️ 3.正确案例❤️❤️❤️ 4.使用jmeter压测 ❤️❤️❤️ 5.建模块 80❤️❤️❤️ 6.如何解决上面问题 ❤️❤️❤️ 7.对8001进行服务降级❤️❤️❤️ 8.对80进行服务降级 ❤️❤️❤️ 9.通用降级方法❤️❤️…

1.2 day2 IO进程线程

使用fread、fwrite完成文件拷贝 #include <myhead.h> int main(int argc, const char *argv[]) {if(argc!3){printf("参数有误");}//定义并以只写的方式打开两个文件FILE *fpNULL;FILE *cfpNULL;if((fpfopen(argv[1],"w"))NULL){perror("fopen…

学习Vue单文件组件总结

今天主要学习了组件实例对象的一个重要内置关系和单文件组件。先说一下实例对象的内置关系&#xff0c;在这里要对JS中的原型链有一定的基础&#xff0c;Vue构造函数的prototype原型指向的是Vue的原型对象&#xff0c;new出来的Vue实例对__proto__同样指向的是Vue的原型对象&am…

harbor自建san证书

1.创建证书存放目录 mkdir -p /opt/harbor/harbor/cert && cd /opt/harbor/harbor/cert 2.拷贝openssl配置文件 cp /etc/pki/tls/openssl.cnf /opt/harbor/harbor/cert 3.编辑拷贝出来的openssl配置文件 vi openssl.cnf [ CA_default ] copy_extensions copy …

异地环控设备如何远程维护?贝锐蒲公英解决远程互联难题

青岛某企业致力于孵化设备、养禽设备和养猪设备的研发、生产和服务&#xff0c;历经三十多年发展&#xff0c;目前已成长为行业主要的养殖装备及工程服务提供商&#xff0c;产品覆盖养殖产业链中绝大多数环节&#xff0c;涉及自动化设备、环控设备、整体解决方案等。 在实际应用…

基于Rangenet Lib的自动驾驶LiDAR点云语义分割与可视化

这段代码是一个C程序&#xff0c;用于处理来自KITTI数据集的激光雷达&#xff08;LiDAR&#xff09;扫描数据。程序主要实现以下功能&#xff1a; 1. **读取和解析命令行参数**&#xff1a;使用Boost库中的program_options模块来定义和解析命令行参数。这包括扫描文件路径、模型…

docker安装postgresql15或者PG15

1. 查询版本 docker search postgresql docker pull postgres:15.3 # 也可以拉取其他版本2.运行容器并挂载数据卷 mkdir -p /data/postgresql docker run --name postgres \--restartalways \-e POSTGRES_PASSWORDpostgresql \-p 5433:5432 \-v /data/postgresql:/var/lib/p…