【优选算法 分治】深入理解分治算法:分治算法入门小专题详解

news2025/1/4 15:05:05

        

 


快速排序算法 


    (1) 快速排序法      



    (2) 快排前后指针     



    (3) 快排挖坑法    



颜色分类


    题目解析   



    算法原理     


算法原理和移动零非常相似


    简述移动零的算法原理    

  • cur 在从前往后扫描的过程中,如果扫描的数符合 f 性质,就把这个数放到 dest 之前的区域;
  • 符合 g 性质则不管,知道 cur 遍历到最后,dest 指针就可以把数组划分成两个部分; 

    解法:三指针     


当 i 遍历结束数组后,整个数组就被排好序了,并且数组以 left,right 两个指针为分界线分成三个部分;


  在遍历的过程中,整个数组会被划分成四个部分:


     对遍历到的 nums[ i ] 的不同元素的分类处理    



    nums[ i ] = 0     


对于上面这种情况,我们只需要 swap( nums[ left + 1] , nums [ i ] ) left++,i ++ 即可,要注意nums[left+1]=1 ;


但是有一种极端情况,就是如果 i = left + 1,并且也符合 nums[ i ] = 0 的情况,我们依旧要执行 swap( nums[ left + 1] , nums [ i ] );

虽然这种极端情况是变成自己和自己交换,但是处理方法不变:交换完成后,left ++ , i ++; 


    nums[ i ] = 1     


此时我们令 i ++ 即可;


    nums[ i ] = 2     


 对于这种情况,我们可以 swap( nums[ i ] , nums[ right - 1 ] ) ,然后 right-- ;


此时需要特别注意,swap( nums[ i ] , nums[ right - 1 ] ) 后,i 是不可以 ++ 的,因为 [ i ,right-1 ] 是待扫描区域,交换后,nums[ i ] 依旧是待扫描元素,所以 i 如果是和 right -1交换的情况,i 是一定不可以++的


    处理细节问题    


    循环终止条件     


当待扫描区域已经没有元素,说明此时数组所有元素已经全部被扫描并且分好类了,结束循环即可,所以循环终止条件是 i != right;


    指针初始化     


根据我们上面的算法原理,为了保证 left 指向 0 区域的最右边, right 区域指向 2 区域的最左边,我们交换元素是不会让 nums[ left ] ,nums[ right ] 亲自和 nums[ i ] 进行交换的: 


所以我们的指针初始化的值如下:

 这样的操作是为了避免漏掉对 nums[ 0 ] ,nums [ n - 1 ] 的扫描;


    编写代码    


    报错原因     

  • 交换元素的方法不对;
  • 并且初始化 left,和 right 的操作没有处理好,会漏掉对 nums[ 0 ] ,nums [ n - 1 ] 的扫描


使用快速排序来排序数组


    题目解析   



    算法原理     


    解法:原始快速排序   


     原始快速排序    


而原始快速排序的方法最核心的步骤,就在于根据基准元素进行数据划分的过程,这个步骤的名字是 partation; 


但是,如果使用快速排序的数组有重复元素,那么快排的时间复杂度就会退化: 


    解法:用" 数组分三块 " 的思想实现快速排序    


这是基于原始快速排序的优化策略,用于应对快速排序的数组有大量重复元素的情况,从对数组分成两块划分为分成三块: 

在数组出现大量重复元素时,通过把数组分成三段,可以把时间复杂度从 O(N^2) 降到 O(N);


    分类讨论     


 我们通过递归不断对划分的小区域排序,最终得到排好序的数组;


    优化:用随机的方式选择基准元素    


如果要想让快排的时间复杂度趋于 O(N* logN) ,就需要随机地选择基准元素;

 随机选择基准元素,就是给我们一个数组,我们要等概率地返回区间上的任意一个数;


如何随机地选择一个基准元素呢? 


    编写代码    


    准备工作    


传入要排序的左区间和右区间:

如果 L >= R,则说明排序的区间要么只有一个,要么这个区间没有元素,说明整个数组已经排序完毕;  


    随机生成基准元素 & 初始化下标    


要记一下如何生成随机数下标,以及要特别注意这里的初始化 left != -1 ,right != nums.length:



    把数组分三块     



[ left + 1 , right - 1 ] 这块区域用于存与生成的随机基准元素值相同的元素并且以这块区域为基准,把其他数组元素分到这三块区域对应的区域;

可以记一下这个 partation 的过程,这是快排的核心逻辑;


     [ L , left ] & [ right , R ] 进行排序    


根据 nums[ i ] 与 key 的大小关系,把所有元素放到 [ left + 1 , right - 1 ] 的两边(两边还是乱序的),再对两边区域进行递归排序即可; 


快速选择算法 


数组中的第K个最大元素


    题目解析   



    算法原理     



    解法:基于快排实现快速选择算法   


     快速排序 Partation   


本题是要找出数组中第 K 大的元素 KthLarges,那么我们可以根据基准元素,来判断 KthLarges 是落在上面三个区域的哪一个区域,然后对这个区域进行继续进行 Partation,继续找 KthLarges;


   根据 K 的值分情况讨论,确定 KthLarges 的区间位置    


我们先分别设三个区间的元素个数: 



    处理细节问题    



我们解释一下这种情况: 


    编写代码    


    先根据快排 partation 原理,把数组分三块    



    根据 k 的值来决定递归三块中的哪一块区域     



最小的k个数


    题目解析   



    算法原理     


    解法一:直接排序   


时间复杂度 O( N * logN)  


    解法二:使用大根堆( 求最小的前K个数)   


时间复杂度 O( N * logK ) 


    解法三:快速选择算法   


时间复杂度趋于 O( N ) ,因为是随机选择基准元素,步骤如下:


    对 K 的值分类讨论     



     (1) a > k    



     (2) b >= k    


这种情况我们直接返回前面两个区域的前 k 个小的元素即可,不用关心 [ L , right ] 区间的顺序;


     (3) k > b+c    



通过这三种情况,我们就能明显的感受到快速选择排序是最优的解法;

比如第三种情况,此时[ L ,right ] 区间是没有排序的,但是我们依旧已经找到了最小的 k 个元素的一部分;而其他两种情况都是要对数组的每一个元素进行排序;


    编写代码    


我们是对 nums 的前 k个元素进行排序,把然后把 nums 的前 k 个元素赋值个 ret,然后返回;


 ​​​​报错原因:粗心导致 left & right 指针的初始值写错了



归并排序 


  1. 当数组块被分成每一块都只有一个元素,分块结束;
  2. 用双指针合并两个有序数组;
  3. 归并排序的决策树非常像二叉树的后续遍历( 左右根 ),而快排则类似前序遍历(先对数组整体粗糙分一遍,再去两边区域细分);

使用归并排序来排序数组


    题目解析   


    编写代码    


 


    优化:辅助数组设置为全局变量     


把 tmp 设置为全局,就不用在每次递归时,创建一个新的 tmp ,减少时间开销; 


    int[] nums, tmp ;

    public int[] sortArray(int[] _nums) {
        nums = _nums;
        tmp = new int[nums.length]; 
        mergeSort(0, nums.length - 1);
        return nums;
    }

    public void mergeSort(int left ,int right){
        if(left >= right) return;

        int mid = (left + right) / 2;

        mergeSort(left , mid);
        mergeSort(mid + 1 , right);

        int cur1 = left; 
        int cur2 = mid + 1; 
        int i = 0 ; 

        while(cur1 <= mid && cur2 <= right){
            tmp[i++] = nums[cur1] <= nums[cur2] ? nums[cur1++] : nums[cur2++] ; 
        }

        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]; 
    }


数组中的逆序对


    题目解析   



    算法原理     


    解法一:暴力枚举   


固定其中一个数,在这个数后面区间找比这个数小的数,来构成一个逆序对; 使用两层 for 循环即可,但是会超时;


    解法二:归并排序   


    算法原理    



    (1) 左半部分 + 右半部分 +  一左一右     


 本质还是暴力枚举,逆序对个数 = a + b + c 


    (2) 左半部分 + 左排序 + 右半部分 + 右排序 + 一左一右 + 排序     


对左右两边排好序,再一左一右挑数组成逆序对,并不影响结果:


     本题为什么可以利用归并排序解决问题?     



并且通过这步操作,我们得出本题最终的解法,就是需要通过归并排序求逆序对个数;  

我们在找出左右两边的逆序对个数时,先对两边排序再一左一右,会非常快:


    一左一右查找逆序对策略优化     


我们在之前找左右两边区域的所有逆序对时,就刻意地对左右两边进行排序,找完左右两边的逆序对时,nums 的两边区域的顺序已经被排序成升序,此时我们需要一左一右寻找逆序对


    策略一 : 判断 nums[cur1]>nums[cur2]  + 升序      


    统计一左一右逆序对的固定策略    


固定 cur2,  只要 cur1 一移动到 nums[cur1]>nums[cur2]位置,就统计符合要求的区间的元素个数,cur2++;


假设我们固定 cur2,要在左边找有多少个比 nums[ cur2 ] 大的元素

令 cur1 在左边区域中从左到右扫描,找出第一个 nums[ cur1 ] > nums[ cur2 ] 的 cur1  


    (1) nums[ cur1 ] <= nums[ cur2 ]     


我们直接让 cur1++ ,并且为了让数组有序,我们要把 nums[cur1] 放入辅助数组中,方便后续排序,继续往后找即可:


    (2) nums[ cur1 ] > nums[ cur2 ]      


出现这种情况,对于两边升序区域,当 nums[ cur1 ] > nums[ cur2 ] ,说明此时 cur1 指向的元素,是左边区域第一个比 nums[ cur2 ] 大的元素;cur1 后面所有元素都比 nums[ cur2 ] 大

也因为是升序排序,在 cur1 一走到第一个合法位置时, ret += mid - cur1 + 1即可;

本轮 cur2 的所有能拿到的逆序对就已经统计好了,此时 cur2++ 即可;


    如果策略一使用降序排列会出现的问题    


对于降序排序,我们依旧是需要固定 cur2;

我们依旧需要采取【 只要 cur1 一移动到 nums[cur1]>nums[cur2] 的位置,就统计符合这个要求的区间的元素个数】的策略


如下图的情况:

因为是降序排序的,所以 cur2 不能因为 nums[cur1]>nums[cur2]  就马上++ ,还必须看下一个 cur1 能否满足  nums[cur1]>nums[cur2] :

因为采取了【 只要 cur1 一移动到 nums[cur1]>nums[cur2] 的位置,就统计符合这个要求的区间的元素个数】的策略,因此就会造成大量的重复计算;


cur1 一移动,判断是否大于 cur2,如果判断成立,马上就让 ret += cur1 - left +1,所以会出现多次重复计算;

所以使用策略一,在排序数组时不能用降序,避免大量重复计数,所以只能用升序;


    策略二 : nums[cur1]<nums[cur2]  + 降序      


    统计一左一右逆序对的固定策略     


固定 cur2,  只要 cur1 一移动到 nums[cur1]<nums[cur2]位置,就统计符合要求的区间的元素个数,cur2++;



    编写代码    


    策略一 + 升序     


    准备工作    


    主逻辑    


    策略二 + 降序      




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


    题目解析   



    算法原理     


这道题的本质,还是类似于求逆序对的个数; 


    解法:归并排序(左半部分 + 左排序 + 右半部分 + 右排序 + 一左一右 + 排序)   


    一左一右策略:nums[ cur1 ] < nums[ cur2 ] + 降序(上一题策略二)    


  • nums[ cur1] <= nums[ cur2 ] ,cur2++;修改对应的辅助数组;
  • nums[ cur1 ] > nums[ cur2 ],把 nums[ cur1 ] 对应 ret[ ] 下标的元素加上 right - cur2 + 1; 修改对应的辅助数组;

    处理细节问题    


我们要找到 nums[ cur1 ] 对应 ret[ ] 的对应下标 ,因为我们排序的时候,下标已经乱了;


如果我们使用哈希表来设置 < nums 元素 ,  ret 下标 > 的映射,如果 nums有重复元素,哈希表无法存储重复元素,就无法解决下标混乱的问题


所以我们设置与 nums 同等规模的数组 index[ ],表示 nums 未排序时元素的原始下标,


不管 nums 因为排序,里面的元素怎么移动,index 对应的元素绑定移动

那如何让 nums[] 和 index[] 绑定移动呢?我们需要创建两个辅助数组 tmp,来让它们同步移动;

最后,让 ret [ index[ i ] ] +=  right - cur2 + 1 即可;


    编写代码    


    准备工作    



     核心逻辑    


报错原因:对于降序排序的一左一右操作不熟练,判断条件写错了 ;



翻转对


拓展 


     直接插入排序    



     选择排序     



    冒泡排序     



 

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

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

相关文章

Qt5 中 QGroupBox 标题下沉问题解决

我们设置了QGroupBox 样式之后,发现标题下沉了,那么如何解决呢? QGroupBox {font: 12pt "微软雅黑";color:white;border:1px solid white;border-radius:6px; } 解决后的效果 下面是解决方法: QGroupBox {font: 12pt "微软雅黑";color:white;bo…

sentinel-请求限流、线程隔离、本地回调、熔断

请求限流&#xff1a;控制QPS来达到限流的目的 线程隔离&#xff1a;控制线程数量来达到限流的目录 本地回调&#xff1a;当线程被限流、隔离、熔断之后、就不会发起远程调用、而是使用本地已经准备好的回调去提醒用户 服务熔断&#xff1a;熔断也叫断路器&#xff0c;当失败、…

体验Cursor一段时间后的感受和技巧

用这种LLM辅助的IDE一段时间了&#xff0c;断断续续做了几个小项目了&#xff0c;总结一下整体的感受和自己的一些使用经验。 从Cursor开始又回到Cursor 第一个真正开始使用的LLM的辅助开发IDE就是Cursor&#xff0c;Github的Copilot支持尝试过&#xff0c;但是并没有真正的在…

【数据仓库】hadoop3.3.6 安装配置

文章目录 概述下载解压安装伪分布式模式配置hdfs配置hadoop-env.shssh免密登录模式设置初始化HDFS启动hdfs配置yarn启动yarn 概述 该文档是基于hadoop3.2.2版本升级到hadoop3.3.6版本&#xff0c;所以有些配置&#xff0c;是可以不用做的&#xff0c;下面仅记录新增操作&#…

宽带、光猫、路由器、WiFi、光纤之间的关系

1、宽带&#xff08;Broadband&#xff09; 1.1 宽带的定义宽带指的是一种高速互联网接入技术&#xff0c;通常包括ADSL、光纤、4G/5G等不同类型的接入方式。宽带的关键特点是能够提供较高的数据传输速率&#xff0c;使得用户可以享受到稳定的上网体验。 1.2 宽带的作用宽带是…

[2025] 如何在 Windows 计算机上轻松越狱 IOS 设备

笔记 1. 首次启动越狱工具时&#xff0c;会提示您安装驱动程序。单击“是”确认安装&#xff0c;然后再次运行越狱工具。 2. 对于Apple 6s-7P和iPad系列&#xff08;iOS14.4及以上&#xff09;&#xff0c;您应该点击“Optinos”并勾选“允许未经测试的iOS/iPadOS/tvOS版本”&…

Linux SVN下载安装配置客户端

参考&#xff1a; linux下svn服务器搭建及使用&#xff08;包含图解&#xff09;_小乌龟svn新建用户名和密码-CSDN博客 1.ubuntu安装svn客户端 “subversion” sudo apt-get update sudo apt-get install subversion 查看安装的版本信息&#xff0c;同时看是否安装成功 s…

【Windows】Windows系统查看目录中子目录占用空间大小

在对应目录下通过powershell命令查看文件夹及文件大小&#xff0c;不需要管理员权限。 以下为方式汇总&#xff1a; 方式1&#xff08;推荐&#xff0c;免费下载使用&#xff0c;界面友好&#xff09;&#xff1a; 使用工具以下是一些第三方工具treesize_free https://www.ja…

【论文阅读笔记】IceNet算法与代码 | 低照度图像增强 | IEEE | 2021.12.25

目录 1 导言 2 相关工作 A 传统方法 B 基于CNN的方法 C 交互方式 3 算法 A 交互对比度增强 1)Gamma estimation 2)颜色恢复 3)个性化初始η B 损失函数 1)交互式亮度控制损失 2)熵损失 3)平滑损失 4)总损失 C 实现细节 4 实验 5 IceNet环境配置和运行 1 下载…

L25.【LeetCode笔记】 三步问题的四种解法(含矩阵精彩解法!)

目录 1.题目 2.三种常规解法 方法1:递归做 ​编辑 方法2:改用循环做 初写的代码 提交结果 分析 修改后的代码 提交结果 for循环的其他写法 提交结果 方法3:循环数组 提交结果 3.方法4:矩阵 算法 代码实践 1.先计算矩阵n次方 2.后将矩阵n次方嵌入递推式中 提…

小白投资理财 - 看懂 PE Ratio 市盈率

小白投资理财 - 看懂 PE Ratio 市盈率 什么是 PE RatioPE 缺陷PE 优点总结 无论是在菜市还是股票市场&#xff0c;每个人都想捡便宜&#xff0c;而买股票就像市场买菜&#xff0c;必须货比三家&#xff0c;投资股票最重要就是要知道回本时间要多久&#xff0c;市场上很多时候散…

python利用selenium实现大麦网抢票

大麦网&#xff08;damai.cn&#xff09;是中国领先的现场娱乐票务平台&#xff0c;涵盖演唱会、音乐会、话剧、歌剧、体育赛事等多种门票销售。由于其平台上经常会有热门演出&#xff0c;抢票成为许多用户关注的焦点。然而&#xff0c;由于票务资源的有限性&#xff0c;以及大…

SQL-leetcode-183. 从不订购的客户

183. 从不订购的客户 Customers 表&#xff1a; -------------------- | Column Name | Type | -------------------- | id | int | | name | varchar | -------------------- 在 SQL 中&#xff0c;id 是该表的主键。 该表的每一行都表示客户的 ID 和名称。 Orders 表&#…

充电桩语音提示芯片方案-支持平台自定义语音NV128H让充电更便捷

随着工业化与城市化进程的加速推进&#xff0c;传统燃油汽车的数量急剧攀升&#xff0c;这直接导致了石油资源的过度开采与消耗。石油&#xff0c;作为不可再生的化石燃料&#xff0c;其储量日益枯竭&#xff0c;价格波动频繁&#xff0c;给全球能源安全带来了前所未有的挑战。…

Android 旋转盘导航栏

1.直接上源码&#xff1a; package com.you.arc;import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Point; import android.graphics.RectF; import android.support…

手搓一个ChatUI需要分几步

只关注项目代码的同学可以直接跳转目录中的正文部分&#xff0c;查看项目仓库和功能介绍。 引言 Chatbot的UI界面设计&#xff0c;是和传统软件不同的&#xff1a;都是当面一个简洁的对话框&#xff0c;框里预备着热乎的工具&#xff0c;可以随时更新。 像我这样做工的牛马&a…

低代码开发深度剖析:JNPF 如何引领变革

在当今数字化转型加速的时代&#xff0c;低代码开发已成为众多企业提升效率、降低成本的关键利器。它打破了传统开发模式的高门槛和冗长流程&#xff0c;让应用开发变得更加高效、灵活与普惠。 低代码开发的核心优势 低代码开发平台通过可视化的操作界面&#xff0c;减少了对…

uniapp实现APP、小程序与webview页面间通讯

需求&#xff1a; 1、需要在Uniapp开发的APP或小程序页面嵌入一个H5网页&#xff0c;需要拿到H5给APP传递的数据。 2、并且这个H5是使用vuevant开发的。&#xff08;其实跟使用uniapp开发H5一样&#xff09; 实现步骤&#xff1a; 1、首先需要兼容多端和App端&#xff0c;因…

网络游戏之害

网络游戏之害&#xff1a; 网络游戏于今之世风靡四方&#xff0c;其娱人耳目、畅人心怀之效&#xff0c;固为人知&#xff0c;然所藏之害&#xff0c;若隐伏之暗潮&#xff0c;汹涌而至时&#xff0c;足以覆舟&#xff0c;尤以青年为甚&#xff0c;今且缕析其害&#xff0c;以…

数据库的创建与删除:理论与实践

title: 数据库的创建与删除:理论与实践 date: 2024/12/31 updated: 2024/12/31 author: cmdragon excerpt: 在当今的数字时代,数据的管理和存储变得尤为重要。数据库作为数据存储的结构化方案,为数据的增删改查提供了系统化的方法。在一个典型的数据库管理系统中,创建和…