二分判定+选插冒排序+归并快速堆希尔+计数排序

二分力扣题

一:搜索二维矩阵

74. 搜索二维矩阵

按照题意:直接利用二维数组转换成一维数组进行求解

在这里插入图片描述

方法一:普通等于的二分查找

class Solution {
public:bool searchMatrix(vector<vector<int>>& matrix, int target) {this->m = matrix.size();this->n = matrix[0].size();int l=0;int r=m*n-1;  //(m-1)*n+(n-1)  r坐标:(m-1,n-1)int mid;while(l<=r){mid=(l+r)>>1;auto [i,j]=getIndex(matrix,mid);//三种分支if(matrix[i][j]<target) l=mid+1;else if(matrix[i][j]>target)  r=mid-1;else return true;//==}//没找到return false;}private:int m, n;// 一维转换二维pair<int, int> getIndex(vector<vector<int>>& matrix, int index) {return {index / n, index % n};}
};

方法二:按照求最后一个等于

class Solution {
public:bool searchMatrix(vector<vector<int>>& matrix, int target) {this->m = matrix.size();this->n = matrix[0].size();int l=0;int r=m*n-1;  //(m-1)*n+(n-1)  r坐标:(m-1,n-1)int mid;//最后一个等于(扩大l)while(l<=r){mid=(l+r)>>1;auto [i,j]=getIndex(matrix,mid);if(matrix[i][j]<=target){//最后一个<=l=mid+1;}else{r=mid-1;}}//r要求>=0if(r<0) return false;auto [i,j]=getIndex(matrix,r);//下标已经合法了return matrix[i][j]==target?true:false;}private:int m, n;// 一维转换二维pair<int, int> getIndex(vector<vector<int>>& matrix, int index) {return {index / n, index % n};}
};

也可以按照第一个等于,注意if条件是“>=“

//第一个等于(缩小r)while(l<=r){mid=(l+r)>>1;auto [i,j]=getIndex(matrix,mid);//合法就缩小rif(matrix[i][j]>=target){r=mid-1;}else{l=mid+1;}}//l要求<= ---(m*n-1)if(l>m*n-1) return false;auto [i,j]=getIndex(matrix,l);//注意是l//下标已经合法了return matrix[i][j]==target?true:false;

二:不重复元素找最小值

153. 寻找旋转排序数组中的最小值

不是只有单调序列才能二分查找,只要题目属于分段单调的,而且能够找到分界点就可以二分。

本题就是一个不含重复元素的单调序列,把它的后半部分移到了数组开头位置。

比如:[1,3,5,7,9]->[7,9 || 1,3,5]

序列具备:两段都是单调函数,而且前一段序列值一定大于后一半

在这里插入图片描述

想要找的最小值满足:

  • 它一定比最右端元素小
  • 而且是比最右端元素小的最小值

而且:比最右端元素大的值一定不是最小值

相当于:找第一个<=最右端元素的值

//6789//    1234//找到第一个<=最右端的数int findMin(vector<int>& nums) {int l=0;int r=nums.size()-1;while(l<r){int mid=(l+r)>>1;if(nums[mid]<=nums[r]){r=mid;//注意保留mid}else{l=mid+1;}}return nums[l];//注意返回的是下标对应的值}

也可以比较第一个值nums[0],但是要注意,如果[l,r]不存在转折,也就是满足递增提前判断

如果mid对应值>=nums[0],最低谷一定在mid右端,l=mid+1

如果mid对应值值<nums[0],最低谷可能是mid或mid左边,r=mid

在这里插入图片描述

int findMin(vector<int>& nums) {int l=0;int r=nums.size()-1;while(l<r){//如果[l,r]是一段递增if(nums[l]<=nums[r]){return nums[l];}int mid=(l+r)/2;//存在低谷if(nums[mid]>=nums[0]){l=mid+1;}else{r=mid;}}return nums[l];}

三:搜索旋转数组值

33. 搜索旋转排序数组

先确定mid再寻找值,确定mid与target无关,只跟两侧有关

在这里插入图片描述

本题的数组序列是递增数列的一部分移动到了前面

比如:1 3 5 7 9 -> 7 9 || 1 3 5 满足:两部分都递增,而且前一部分比后一部分大

解题时:

首先判断mid在哪一个递增分段上,有两种可能:

  1. 如果mid落在左分支,即:nums[mid]>=nums[l]

在这里插入图片描述

此时:只需要判断target在红色还是不在红色

nums[l]<=target<nums[mid]

  • 在红色,r=mid-1
  • 不在红色,l=mid+1
  1. 如果mid落在右分支,即:nums[mid]<nums[l]

在这里插入图片描述

同上面,判断target是否落在红色区间

nums[mid]<=target<nums[r]

  • 在红色,l=mid+1
  • 不在红色,r=mid-1

代码:

 int search(vector<int>& nums, int target) {int l = 0;int r = nums.size() - 1;while (l <= r) {int mid = (l + r) >> 1;// 返回结果if (nums[mid] == target) {return mid;}if (nums[mid] >= nums[l]) {// 判断taget所在位置if (target >= nums[l] && target < nums[mid]) {r = mid - 1;} else {l = mid + 1;}} else {if (target > nums[mid] && target <= nums[r]) {l = mid + 1;} else {r = mid - 1;}}}// 没找到return -1;}

还可以判断<=

while(l<=r){int  mid=(l+r)>>1;if(nums[mid]==target) return mid;else if(nums[mid]<=nums[r]){//midif(nums[mid]<target && target<=nums[r]){l=mid+1;}else{r=mid-1;}}else{if(target>=nums[l]&&nums[mid]>target){r=mid-1;}else{l=mid+1;}}}

也就是判断mid位置就两种

  • 大于等于nums[l]
  • 小于等于nums[r]

都只判断一个就可以确定mid

四:选举人

911. 在线选举

思路:

  • 既然是统计大小,一定会用到查找表(set或map)

  • 题目给出的是某一时刻投给了哪个人,那么TopVotedCandidate()计算:某一个时刻哪个人票数最多

  • 然后q(t)计算是任意t时间点的获胜人数,只有某一时间点获胜人数的信息,所以二分查找:

    —最后一个<=t的时间点,然后直接返回对应获胜者即可

  1. map统计频次,mp[人]=票数,用winners存储当前时间点获胜的人
class TopVotedCandidate {
public:TopVotedCandidate(vector<int>& persons, vector<int>& times) {this->times=times;int maxWinner=0;//当前最大票数for(auto& val:persons){mp[val]++;//对应人票数+1if(mp[val]>=mp[maxWinner]){//题目要求相等取最近获胜者,需要更新maxWinner=val;}winners.push_back(maxWinner);}}int q(int t) {//查找最后一个<=t的时刻int l=0;int r=times.size()-1;while(l<r){int mid=(l+r+1)>>1;//别忘了+1if(times[mid]<=t){l=mid;//扩大l}else{r=mid-1;}}return winners[l];}
private:unordered_map<int,int> mp;//mp[选举人]=票数vector<int> winners;//当前t时刻的获胜者vector<int> times;//为q(t)传参
};

或者也可以定义最大值记录当前获胜者的最大票数和得到最大票数的优胜者

int maxVotes=0;//当前最大票数int top=0;//要存入的获胜者for(auto& val:persons){mp[val]++;//对应人票数+1if(mp[val]>=maxVotes){//题目要求相等取最近获胜者,需要更新maxVotes=mp[val];top=val;//当前人}winners.push_back(top);}

方法二:

直接计算:mp[当前时间点]=获胜者,只是需要开辟新数组来记录最大值

class TopVotedCandidate {
public:TopVotedCandidate(vector<int>& persons, vector<int>& times) {int maxVotes=0;votes=vector<int>(times.size());for(int i=0;i<times.size();i++){votes[persons[i]]++;//persons[i]这个人的票数+1//因为出现i-1if(i==0){maxVotes=votes[persons[i]];mp[times[i]]=persons[i];continue;}if(votes[persons[i]]>=maxVotes){maxVotes=votes[persons[i]];//当前时刻的的票最多人mp[times[i]]=persons[i];}else{mp[times[i]]=mp[times[i-1]];}}}int q(int t) {while(!mp.count(t)){t--;}return mp[t];}
private://mp[time]=persons;unordered_map<int,int> mp;vector<int> votes;
};/*** Your TopVotedCandidate object will be instantiated and called as such:* TopVotedCandidate* obj = new TopVotedCandidate(persons, times);* int param_1 = obj->q(t);*/

三分查找

问题:

已知函数 𝑓(𝑥) 在区间 [l,r] 上单峰且连续,求 𝑓(𝑥) 在 [l,r]上的极值

也就是说:三分查找适用于存在局部极大值或局部极小值的函数来找到极值点

在这里插入图片描述

​ 如图:在定义域[L,R]内部取两点lmidrmid,整个函数被这两个点分成了三块区域,通过这两个值的大小,来舍弃某一些区间来缩小查找范围

查找过程

在这里插入图片描述

有高峰的函数,只要满足:nums[lmid]<nums[rmid],那么极大值一定在lmid的右边,也就是让l=lmid+1;

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

同理:如果nums[rmid]<nums[lmid],极大值一定在rmid左边,也就是:r=rmid-1

寻找峰值

162. 寻找峰值

为了能保证每次取舍一半区间,让[l,r]内部取的lmid和rmid取值为:

  • lmid=(l+r)>>1,也就是中点
  • rmid=lmid+偏移量,可以是lmid+1

当然lmid和rmid也可以是三等分点

 int findPeakElement(vector<int>& nums) {//这里题目一定会有解,l和r直接取两端即可int l=0;int r=nums.size()-1;while(l<r){//在[l,r]内部取lmid和rmidint lmid=(l+r)>>1;int rmid=lmid+1;//进行区间取舍if(nums[lmid]<=nums[rmid]){l=lmid+1;}else{r=rmid-1;}}return l;//r}

![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=C%3A%5CUsers%5C24732%5CAppData%5CRoaming%5CTypora%5Ctypora-user-images%5Cimage-20240426014557032.png&pos_id=img-x1OB1Skc-1715539346227

这里的等号取不取,对本题而言没有影响

二分判定枚举

二分法还有一个重要应用是 枚举答案,尤其是值域上的二分枚举。

注意:要先确定存在二段性,才可以用二分枚举答案

引例:猜数字

374. 猜数字大小

使用相加除二可能会溢出

mid = (l + r) / 2;

如果 l 和 r 足够大,mid值会导致int 数据类型越界,如果超出int的最大范围 那么结果变成负数

防溢出写法:使用减法r - l不会超出最大的整型范畴

mid = l + (r - l)/2;
 int guessNumber(int n) {//数字一定在1~n中int l=1;int r=n;//二分猜值while(l<=r){int mid=l+(r-l)/2;//防止溢出int ans=guess(mid);//调用接口//三种if(ans==-1){//要猜的更小r=mid-1;}else if(ans==1){l=mid+1;}else{//==return mid;}}return -1;}

可以选用第一个大于等于或最后一个小于等于,注意mid的向上和向下取值

int guessNumber(int n) {//数字一定在1~n中int l=1;int r=n;//二分猜值while(l<r){int mid=l+(r-l)/2;//防止溢出int ans=guess(mid);//调用接口//找第一个==if(ans<=0){r=mid;}else{l=mid+1;}}return l;}

int guessNumber(int n) {//数字一定在1~n中int l=1;int r=n;//二分猜值while(l<r){//向上取整int mid=l+1+(r-l)/2;//防止溢出int ans=guess(mid);//调用接口//找第一个==if(ans>=0){l=mid;}else{r=mid-1;}}return l;}

对于一个最优化问题:

  • 求解:求出这个最优解
  • 判定:判断这个解是否合法

p问题就是能够多项式时间求出问题的解

np问题就是能够多项式时间验证问题的解

二分答案法

如果解空间具有单调性,那么利用二分加上判定就相当于枚举整个问题的解,这种求出最优解的方法叫二分答案法

也就是通过判定算法枚举解空间,一样可以求出最优解

比如:

猜数问题,在解空间每次选择中间值通过判定猜的数是大了还是小了,一样可以得到最终结果

一:运送包裹能力

1011. 在 D 天内送达包裹的能力

也就是想找到一个值W,这个值能满足在days天顺序运送完货物,而且W是最小的

这道题满足单调性,W值越大,那么越能在days天运送完货物

思路:首先写出判定函数isValid(),该函数可以判定解的合法性,然后用二分查找结合该函数就能找到最小值

注意:

  1. W最小值就是所有货物取最大,也就是一下只运送一个货物,但是days会很长
  2. W最大值就是一次运送完所有货物,但是W值一定不是想要的最小值
  3. 判定函数:就是给定的W值能否满足:在days天内运送完所有货物。

说白了就是枚举W的所有可能,但是由于具备单调性,W能在days天运送完,那么W+1一定也能在days天内运完。所以可以利用二分枚举所有可能,然后来利用判定函数来取舍区间

解:

class Solution {
public:int shipWithinDays(vector<int>& weights, int days) {int l=0;int r=0;//初始化值,l取最小,r取最大for(auto val:weights){l=max(l,val);r+=val;}//二分给定W值while(l<r){int mid=(l+r)>>1;if(isValid(weights,days,mid)){r=mid;//缩小mid值}else{l=mid+1;}}return l;}
private://给定值W,能否在days天内,完成所有物品运输bool isValid(vector<int>& weights,int days,int W){//尽量把货物塞满W,多了就下一天再搬(贪心)int sum=0;int time=1;//第一天for(int i=0;i<weights.size();i++){if(sum+weights[i]<=W){sum+=weights[i];}else{//这天超过了,开始下一天的搬运time+=1;sum=weights[i];}}return time<=days;//只要time比规定时间小就完成}
};
二:分割数组最大值

410. 分割数组的最大值

这道题其实和运送包裹完全一样。

思路:

  1. 给定一个用函数来判定:值不超过V的数字和划分一组,看组数是否小于等于k
  2. 然后利用二分来进行求解,取满足判定函数的最小mid值(缩小l)
class Solution {
public:int splitArray(vector<int>& nums, int k) {int l=0;int r=0;//最小l:每一个单组成组;最大r:只划分一组for(auto& val:nums){l=max(l,val);r+=val;}//二分while(l<r){int mid=(l+r)>>1;if(isValid(nums,k,mid)){r=mid;}else{l=mid+1;}}return l;}
private://将nums以<=V划分组,看划分的个数是否<=kbool isValid(vector<int>& nums,int k,int V){int sum=0;int time=1;//从第一组开始分for(int i=0;i<nums.size();i++){if(sum+nums[i]<=V){sum+=nums[i];}else{//重新划分新组time++;sum=nums[i];}}return time<=k;}
};

这里可以二分查找的原因:

解具备单调性,因为假设划分k组后某组最大值是V,那么V+1,V+2,V+N也一定满足题目的要求。

也就是说:可以用二分枚举V然后通过判定得到能符合要求的最小值。

假设划分k组后,最大值为V。那么划分k-n组一定满足最大值小于V。

题目要求:最大值最小,所以可以找到恰好符合的临界条件

三:制作花最少天数

1482. 制作 m 束花所需的最少天数

​ 题目的解满足单调性:

假定T天就能完成k组m束花的要求,那么T+1,T+2,…也一定可以

假设T天可以采摘超过m束花,那么最优解可能在T-1,T-2,…里面取得

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=C%3A%5CUsers%5C24732%5CAppData%5CRoaming%5CTypora%5Ctypora-user-images%5Cimage-20240430021933640.png&pos_id=img-LQ9vrNp4-1715539346228

class Solution {
public:int minDays(vector<int>& bloomDay, int m, int k) {int MAX=(1e+9)+1;int l=1;//第一天就全采摘了int r=MAX;//题目给定最大+1while(l<r){int mid=(l+r)>>1;if(isValid(bloomDay,m,k,mid)){r=mid;//缩小r,因为求最短T}else{l=mid+1;}}return l==MAX?-1:l;}
private://给定时间T,问:能否在T天内采摘m束花,每束花必须满足连续相邻k朵bool isValid(vector<int>& bloomDay,int m,int k,int T){int continuous=0;//计算连续int time=0;//初始化不能采摘for(int i=0;i<bloomDay.size();i++){if(bloomDay[i]<=T){//开花了continuous++;//满足条件更新结果if(continuous==k){time++;continuous=0;}}else{continuous=0;//断了,重新计数}}return time>=m;//至少要采摘m}
};

四:能吃完香蕉的最少天数

875. 爱吃香蕉的珂珂

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

也就是定义好速度V,然后二分枚举求出整个最小V

题目中:

如果pile[i]<V,也就是1h能吃V根数量多于该堆数量,那么取时间一小时

所以相当于pile[i]/V向上取整

可以利用余数来选择是否加一,也可以:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

class Solution {
public:int minEatingSpeed(vector<int>& piles, int h) {int l=1;int r=1e9;while(l<r){int mid=(l+r)>>1;if(isValid(piles,h,mid)){r=mid;//缩小r}else{l=mid+1;}}return l;}
private://给定速度V:在h小时内能否吃完所有香蕉bool isValid(vector<int>& piles,int h,int V){int hour=0;//千万别忘了初始值0for(int i=0;i<piles.size();i++){if(piles[i]%V==0){hour+=piles[i]/V;}else{hour+=piles[i]/V+1;}}return hour<=h;}
};

计算hour还可以:

for(auto pile:piles){hour+=pile/V+(pile%V==0?0:1);    }

也可以:

for(int i=0;i<piles.size();i++){hour+=(piles[i]+V-1)/V;// hour+=(piles[i]-1)/V+1;}

注意:

因为题目限制了珂珂一个小时之内只能选择一堆香蕉吃,因此速度最大值就是这几堆香蕉中,数量最多的那一堆。

int r=1;

for(auto val:piles) r=max(r,val);

排序

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

基于比较的排序算法,时间复杂度:不能突破O(NlogN)

简单证明:

在这里插入图片描述

  • 比较排序

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 非比较排序

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

选插冒三种初级排序

选择排序
// 选择排序vector<int> sortArray(vector<int>& nums) {int n = nums.size();for (int i = 0; i < n - 1; i++) {int minIndex = i;for (int j = i + 1; j < n; j++) {if (nums[j] < nums[minIndex]) {minIndex = j;}}swap(nums[i], nums[minIndex]);}return nums;}
插入排序

在这里插入图片描述

  • 直接交换
//插入排序vector<int> sortArray(vector<int>& nums) {int n=nums.size();for(int i=1;i<n;i++){//i从1开始,默认第一个有序//结束条件是:j>0,因为访问j-1for(int j=i;j>0 && nums[j]<nums[j-1];j--){swap(nums[j],nums[j-1]);}}return nums;}
  • 优化

可以不直接交换,记住值,然后后一个值等于前一个值,停止时,值改成最先记录的

//插入排序vector<int> sortArray(vector<int>& nums) {int n=nums.size();for(int i=1;i<n;i++){//i从1开始,默认第一个有序int e=nums[i];int j;for(j=i;j>0 && nums[j-1]>e;j--){nums[j]=nums[j-1];}nums[j]=e;}return nums;
冒泡排序

把大的依次放到后面,每一次冒泡都会得到一个最大值,冒泡次数=数组长度-1

在这里插入图片描述

// 冒泡排序vector<int> sortArray(vector<int>& nums) {int n = nums.size();for (int i = 0; i < n - 1; i++) {//冒泡长度-1次for (int j = 0; j < n - 1 - i; j++) {if (nums[j] > nums[j + 1]) { // 大的放后面swap(nums[j], nums[j + 1]);}}}return nums;}

当然也可以从n-1开始,小的放前面

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如果在一次遍历中没有进行任何交换,可以提前结束排序,因为这意味着数列已经排序完成。

优化:引入是否交换过的标志

 // 冒泡排序vector<int> sortArray(vector<int>& nums) {int n = nums.size();bool flag=false;//是否交换过for(int i=0;i<n-1;i++){for(int j=0;j<n-1-i;j++){//大的后放if(nums[j]>nums[j+1]){swap(nums[j],nums[j+1]);flag=true;}}if(!flag) break;}return nums;}

归并排序

class Solution {
public:vector<int> sortArray(vector<int>& nums) {mergeSort(nums,0,nums.size()-1);return nums;}
private:void merge(vector<int>& nums,int l,int mid,int r){int i=l;int j=mid+1;int k=0;//创建aux存储合并结果int* aux=new int[r-l+1];while( i<=mid && j<=r){if(nums[i]<nums[j]){aux[k++]=nums[i++];}else{aux[k++]=nums[j++];}}//判断while中是哪个条件不满足while(i<=mid){aux[k++]=nums[i++];}while(j<=r){aux[k++]=nums[j++];}//把aux结果赋值给nums//注意:aux:[0,r-l+1) nums:[l,r]/*for(int i=0;i<r-l+1;i++){nums[i+l]=aux[i];}*/for(int i=l;i<=r;i++){nums[i]=aux[i-l];}}void mergeSort(vector<int>& nums,int l,int r){if(l==r) return;int mid=(l+r)>>1;//分mergeSort(nums,l,mid);mergeSort(nums,mid+1,r);//治:合并两个结果merge(nums,l,mid,r);} 
};

也可以改写merge

条件1:i<=mid 条件2:j<=r

if(条件1不满足)

else if(条件2不满足)

else if (此时判断)//此时条件1和条件2一定满足了

void merge(vector<int>& nums,int l,int mid,int r){int i=l;int j=mid+1;//创建aux存储合并结果int* aux=new int[r-l+1];for(int k=0;k<r-l+1;k++){if(i>mid){aux[k]=nums[j++];}else if(j>r){aux[k]=nums[i++];}//i<=mid && j<=relse if(nums[i]<nums[j]){aux[k]=nums[i++];}else{aux[k]=nums[j++];}}//还原nums//for(int i=l;i<=r;i++){//  nums[i]=aux[i-l];//}for(int i=0;i<r-l+1;i++){nums[i+l]=aux[i];}}

for(int k=0;k<r-l+1;k++){if(i<=mid && j<=r){if(nums[i]<nums[j]){aux[k]=nums[i++];}else{aux[k]=nums[j++];}}else if(i<=mid){//j>raux[k]=nums[i++];}else{aux[k]=nums[j++];}}

初级排序的优化排序

堆排序

简洁实现,直接利用stl

由于是从小到大,可以存值的负数的大根堆,然后赋值取负号

vector<int> sortArray(vector<int>& nums) {priority_queue<int> pq;//首先建堆for(int i=0;i<nums.size();i++){pq.push(-nums[i]);}//出堆for(int i=0;i<nums.size();i++){nums[i]=-pq.top();//负号pq.pop();}return nums;}

当然直接建立小根堆也行: priority_queue<int,vector<int>,greater<int>> pq;//大的沉底


定义采用向下调整函数

思路:

首先对数组原地调整成大顶堆

  • 注意,因为数组从0开始,k的左孩子k*2+1右孩子k *2+2
  • k的父节点:(k-1)/2
  • 从最后一个父节点不断向上调整至根节点就可以得到堆

接下来要从小到大输出:

  • 每次把堆顶元素往后放,然后从0开始重新调整堆
class Solution {
public:vector<int> sortArray(vector<int>& nums) {int n=nums.size();//从第一个非叶子结点向下调整,直至到根for(int i=(n-2)/2;i>=0;i--){shiftDown(nums,n,i);}//从小到大,把根顶后移for(int i=n-1;i>0;i--){swap(nums[0],nums[i]);shiftDown(nums,i,0);//注意:[0,n-2],所以传n-1}return nums;}
private://向下调整---数组长度,从k开始向下调整void shiftDown(vector<int>& nums,int n,int k){//注意:k*2+1是左孩子,k*2+2是右孩子while(k*2+1<n){int j=k*2+1;if(j+1<n && nums[j+1]>nums[j]) j++;//比较父节点和儿子最大结点jif(nums[k]<nums[j]){swap(nums[k],nums[j]);}else{break;}k=j;//k指向交换后的位置}}
};

优化:记录值,赋值代替交换

void shiftDown(vector<int>& nums,int n,int k){//注意:k*2+1是左孩子,k*2+2是右孩子int e=nums[k];while(k*2+1<n){int j=k*2+1;if(j+1<n && nums[j+1]>nums[j]) j++;//比较父节点和儿子最大结点jif(nums[j]>e){nums[k]=nums[j];//父=儿子}else{break;}k=j;//k指向交换后的位置}nums[k]=e;}
希尔排序(不常用)
  • 利用增量分组对插入排序进行优化

这里最后的0如果选用插入排序会跨越整个数组,影响算法性能

希尔排序的做法:

​ 先将元素进行分组,每次先在组内进行排序,尽量让元素可以在早期尽量多地移动。

选择分组的跨度是5,跨度是数组长度的一半。

相同颜色的元素为一组

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 在组内进行插入排序

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 接着将跨度缩小一半,从5变成2,接着重复上述逻辑

在这里插入图片描述

  • 最后,跨度设为1,总体进行插入排序,得到结果。
vector<int> sortArray(vector<int>& nums) {int n=nums.size();int h=n/2;//假设以一半为增量while(h>0){for(int i=h;i<n;i++){//从增量往前插入for(int j=i;j>=h && nums[j]<nums[j-h];j-=h){swap(nums[j],nums[j-h]);}}h=h>>1;//更新h}return nums;}

这里增量先选取数组长度一半,然后每次缩小一半,直至为1

快速排序
  • 快速排序也是一个基于分治的算法

从数组中选取一个中轴元素pivot

  • 把小元素放在pivot左边
  • 把大元素放在pivot右边

然后继续对左边和右边进行快速排序

归并排序:每次对半排序数组,排序好左右数组后,再合并成有序数组

快速排序:先把左右数组调配出,然后对左右数组进行排序

快速排序的分治:

在这里插入图片描述

也就是说:先把元素分成左右两部分后,然后再接着对左右两部分继续使用快排

对于:[L,R]的数组而言,当 L>=R停止递归

方法一:双指针法

这种方法:先移动i或者先移动j都可以

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

class Solution {
public:vector<int> sortArray(vector<int>& nums) {quickSort(nums,0,nums.size()-1);return nums;}
private://数组分成:  (比基准值小的序列  基准  比基准值大的序列)//返回int,基准值不参与比较int quickDetail(vector<int>& nums,int l,int r){//随机选择基准元素,并放在nums[l]位置swap(nums[l],nums[rand()%(r-l+1)+l]);int e=nums[l];//双指针int i=l+1;int j=r;while(1){while(i<=j && nums[i]<=e) i++;while(i<=j && nums[j]>=e) j--;if(i>j) break;swap(nums[i++],nums[j--]);}//j停在小的位置swap(nums[l],nums[j]);return j;}//分治void quickSort(vector<int>& nums,int l,int r){if(l>=r) return;//注意是大于等于//先划分数组int p=quickDetail(nums,l,r);//再对左右两部分进行分治quickSort(nums,l,p-1);quickSort(nums,p+1,r);}
};

也可以按照符合要求的判断

int quickDetail(vector<int>& nums,int l,int r){//随机选择基准元素,并放在nums[l]位置swap(nums[l],nums[rand()%(r-l+1)+l]);int e=nums[l];//双指针int i=l+1;int j=r;while(i<=j){//先j后i也行while(i<=j && nums[j]>=e) j--;while(i<=j && nums[i]<=e) i++;if(i<=j)swap(nums[i++],nums[j--]);}//j停在小的位置swap(nums[l],nums[j]);return j;}
方法二:大于跳过

思路:i指向小于等于初始值的位置,j不断右移

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 遇到>基准值的右移,i不动,j右移
  • 遇到<=基准值的,把j指向值第一个>基准值的位置交换,也就是i后面的第一个元素

----所以:i的位置指向的是最后一个<=基准值

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

//数组:[l,r]int partition(vector<int>& nums,int l,int r){swap(nums[l],nums[rand()%(r-l+1)+l]);int e=nums[l];int i=l;for(int j=i+1;j<=r;j++){if(nums[j]<=e){//这里可以不带等号swap(nums[j],nums[++i]);}}swap(nums[l],nums[i]);return i;}

注意等号无所谓,可以大于等于跳过,也可以只大于就跳过

这种方法存在致命缺陷,就是等于元素过多,会让左右两部分极度不平衡

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

方法三:三路快排

[l+1,lt]维护的是<10的部分[gt,r]维护的>10的部分,从而[lt+1,gt-1]维护等于

i==gt程序停止

1:这是i值>10情况:交换i值和gt-1位置的值,i不变,gt-1

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2:这是i值<10的情况:交换该值和lt+1的位置的值,lt+1,i右移

在这里插入图片描述

class Solution {
public:vector<int> sortArray(vector<int>& nums) {threeWayQuick(nums,0,nums.size()-1);return nums;}
private:void threeWayQuick(vector<int>& nums,int l,int r){if(l>=r) return;//初始化lt,gt使得:[l+1,lt],[gt,r]为空int lt=l;int gt=r+1;int e=nums[l];int i=l+1;while(i<gt){if(nums[i]>e){swap(nums[i],nums[--gt]);}else if(nums[i]<e){swap(nums[i],nums[++lt]);i++;}else{//==e,直接右移i++;}}swap(nums[l],nums[lt]);//分治threeWayQuick(nums,l,lt-1);threeWayQuick(nums,gt,r);}
};

这里等于不需要传值,所以需要两个值,把分治和划分情况写成一个函数

也可以传入pair

pair<int,int> partition(vector<int>& nums,int l,int r){int lt=l;int gt=r+1;int e=nums[l];int i=l+1;while(i<gt){if(nums[i]>e){swap(nums[i],nums[--gt]);}else if(nums[i]<e){swap(nums[i],nums[++lt]);i++;}else{//==e,直接右移i++;}}swap(nums[l],nums[lt]);return {lt-1,gt};}void threeWayQuick(vector<int>& nums,int l,int r){if(l>=r) return;//初始化lt,gt使得:[l+1,lt],[gt,r]为空auto [k,v]=partition(nums,l,r);//分治threeWayQuick(nums,l,k);threeWayQuick(nums,v,r);}

算法的稳定性

若经过排序,这些相同元素相对次序保持不变,则称这种排序算法是稳定的;否则称为不稳定的。

在原序列中,A1=A2,且A1在A2之前,经过排序后,A1仍在A2之前。

稳定性意义:

​ 要排序的内容是一个具备多个数字属性的复杂对象,且原本的初始顺序存在意义,需要在二次排序的基础上保持原有排序的意义。


各排序算法的稳定性:

1、堆排序、快速排序、希尔排序、选择排序是不稳定的排序算法;

2、基数排序、冒泡排序、插入排序、归并排序是稳定的排序算法

非比较类排序

计数排序
  • 简单版—不考虑稳定性

    把数组值大小作为count的下标,统计频次

    然后赋值回给原数组,注意i值是大小

在这里插入图片描述

void simpleCountSort(vector<int>& nums){int n = nums.size();int sz = 0;//数组元素会作为下标,计算出数组长度for(auto val:nums){sz = max(sz, val);}vector<int> count(sz + 1);//数组标号从0开始//遍历数组,统计次数for(auto val:nums){count[val]++;}//最后赋回给numsint j = 0;for (int i = 0; i <= sz;i++){//遍历countif(count[i]!=0){while(count[i]-->0){nums[j++] = i;}}}
}
  • 考虑稳定性

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

计算前缀和,这样:元素为nums[i],则count[nums[i]]-1对应的位置就是存放的正确位置

数组赋值后,把对应频次-1

vector<int> countSort(vector<int>& nums){/*初始化count频次数组*///1:计算大小int sz = 0;for(auto val:nums){sz = max(sz, val);}vector<int> count(sz + 1);/*计算count前缀和*///1:遍历数组for (auto val:nums){count[val]++;}//2:更新前缀for (int i = 0; i < sz;i++){count[i + 1] += count[i];}/*存放结果*///倒着赋值vector<int> res(nums.size());for (int i = nums.size() - 1;i>=0; i--) {res[count[nums[i]] - 1] = nums[i];count[nums[i]]--;//别忘了频次-1}return res;
}
桶排序(了解)—BucketSort
  • 在桶排序里的“桶”是指一种数据结构,代表一个区间范围,用于临时存储排序元素
  • 桶排序是一种分布式排序算法,将待排序数组元素分到有限数量的桶里,每个桶里的数据分别进行排序, 按照桶的顺序将元素合并成有序数组。

桶排序的工作原理可以拆分为 3 步:

  1. 初始化 m 个桶,将 n 个元素分配到 m 个桶中
  2. 对每个桶内的数据分别进行排序,这里可以借助任意排序算法
  3. 按照桶的从大到小的顺序,将所有元素合并成有序数组

例子:

  1. 假设使用的待排序元素整除规则是 nums[i] / 10,分别将待排序数组的每个元素分配到每个桶中。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 对每个桶内的数据分别进行排序,桶内的排序可以借助任意排序算法,比如插入排序、快速排序等。

在这里插入图片描述

  1. 按照桶的从大到小的顺序,将所有元素合并成有序数组。

在这里插入图片描述

习题:颜色分类

75. 颜色分类

采用不稳定的计数排序就行

void countSort(vector<int>& nums){//只有0,1,2,所以key最大2vector<int> count(3);//频次统计for(auto val:nums){count[val]++;}//赋值回去int j=0;for(int i=0;i<=2;i++){if(count[i]!=0){while(count[i]-->0){nums[j++]=i;}}}}

排序力扣题

一:数组的相对排序

1122. 数组的相对排序

方法一:计数排序

用count数组统计arr1的频次,然后遍历arr2输出元素,最后遍历count输出余下元素

 //计数排序vector<int> relativeSortArray(vector<int>& arr1, vector<int>& arr2) {//先统计arr1的元素频次vector<int> count(1001);for(auto &val:arr1){count[val]++;}//先把arr2的元素输出int j=0;for(auto &val:arr2){while(count[val]-->0){arr1[j++]=val;}}//再把剩余元素输出for(int i=0;i<count.size();i++){while(count[i]-->0){arr1[j++]=i;}}return arr1;}

方法二:直接比较

其实就是优先输出arr2顺序的元素,然后然后比较顺序输出

----先用map记录arr2的次序,map[arr2[i]]=i,然后进行比较:

  • 如果两个元素都在arr2中,比较数组次序
  • 如果一个元素在arr2中,输出这个元素
  • 如果都不在arr2中,那么正常比较大小
 vector<int> relativeSortArray(vector<int>& arr1, vector<int>& arr2) {//用map记录arr2相对次序:mp[arr2[i]]=iunordered_map<int,int> mp;for(int i=0;i<arr2.size();i++){mp[arr2[i]]=i;}//对arr1用自定义比较sort(arr1.begin(),arr1.end(),[&](int x,int y)->bool{if(mp.count(x) && mp.count(y)){return mp[x]<mp[y];}else if(mp.count(x)){return true;//要x<y的x}else if(mp.count(y)){return false;//x<y}else{return x<y;}});return arr1;}

当然也可以这样写sort

 //对arr1用自定义比较sort(arr1.begin(),arr1.end(),[&](int x,int y)->bool{if(mp.count(x)){return mp.count(y)?mp[x]<mp[y]:true;}else{//x不再return mp.count(y)?false:x<y;}});

二:数组中第k大的元素

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

方法一:堆排序

注意:最大值是逆序的,第一个最大n-1,…

class Solution {
public:int findKthLargest(vector<int>& nums, int k) {int n=nums.size();for(int i=(n-2)/2;i>=0;i--){shiftDown(nums,n,i);}for(int j=n-1;j>=0;j--){swap(nums[0],nums[j]);shiftDown(nums,j,0);}return nums[n-k];//n-1 n-2}
private:void shiftDown(vector<int>& nums,int n,int m){int e=nums[m];while(m*2+1<n){int j=m*2+1;//注意if和while别用错了if(j+1<n && nums[j+1]>nums[j]) j+=1;if(nums[j]>e){nums[m]=nums[j];}else{break;}m=j;}nums[m]=e;}
};

方法二:快速排序


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

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

相关文章

io_uring的使用示例及其解释

io_uring的使用示例及其解释 1 io_uring机制1.1 io_uring机制1.2 io_uring系统调用接口功能介绍1.2.1 io_uring_setup()&#xff1a;1.2.2 io_uring_enter()&#xff1a;1.2.3 io_uring_register()&#xff1a; 2 liburing2.1 liburing简介2.2 liburing编译2.2.1 liburing的代码…

基础ArkTS组件:导航栏组件(HarmonyOS学习第三课【3.8】)

Navigation 官方文献 Navigation 组件一般作为页面布局的根容器&#xff0c;它提供了一系列属性方法来设置页面的标题栏、工具栏以及菜单栏的各种展示样式。 Navigation 除了提供了默认的展示样式属性外&#xff0c;它还提供了 CustomBuilder 模式来自定义展示样式 说明 该…

OCR技术在历史文献数字化中的革命性作用

随着数字化技术的不断发展&#xff0c;历史文献的数字化已成为保存和传播文化遗产的重要途径。其中&#xff0c;光学字符识别&#xff08;OCR&#xff09;技术在历史文献数字化中发挥了革命性的作用&#xff0c;为研究者提供了更广阔的研究空间&#xff0c;推动了历史学研究的发…

kafka安装及收发消息

kafka需要与zookeeper配合使用&#xff0c;但是从2.8版本kafka引入kraft&#xff0c;也就是说在2.8后&#xff0c;zookeeper和kraft都可以管理kafka集群&#xff0c;这里我们依然采用zookeeper来配合kafka。 1、首先我们下载zookeeper 下载地址为 https://zookeeper.apache.org…

三. TensorRT基础入门-剖析ONNX架构并理解ProtoBuf

目录 前言0. 简述1. 执行一下我们的python程序2. ONNX是什么&#xff1f;3. onnx中的各类Proto3.1 理解onnx中的ValueInfoProto3.2 理解onnx中的TensorProto3.3 理解onnx中的NodeProto3.4 理解onnx中的AttributeProto3.5 理解onnx中的GraphProto3.6 理解onnx中的ModelProto 4. …

算法提高之单词接龙

算法提高之单词接龙 核心思想&#xff1a;dfs 预处理每两个字符串之间最短的公共部分长度 求最短公共 最终字符串是最长 dfs所有开头字符串 #include <iostream>#include <cstring>#include <algorithm>using namespace std;const int N 25;int g[N][N…

Feign 和 OpenFeign 的区别

Feign 和 OpenFeign 都是用来进行服务间调用的客户端库&#xff0c;它们旨在简化HTTP API客户端的编写过程&#xff0c;使得编写对外部服务的接口就像调用本地方法一样简单。尽管它们有相似之处&#xff0c;但也存在一些关键差异&#xff1a; 归属和演进&#xff1a; Feign 最初…

大规模 RGB LED灯控系统 Lumos:创新与智能化的融合

灯控系统&#xff1a;创新与智能化的融合 在现代照明技术不断进步的背景下&#xff0c;灯控系统的应用已经从简单的开关控制&#xff0c;发展到能够进行复杂程控操作的智能化管理。我们推出的新一代灯控解决方案&#xff0c;凭借其高度的可配置性和跨平台兼容性&#xff0c;已…

虚拟化数据恢复—误还原虚拟机快照怎么办?怎么恢复最新虚拟机数据?

虚拟化技术原理是将硬件虚拟化给不同的虚拟机使用&#xff0c;利用虚拟化技术可以在一台物理机上安装多台虚拟机。误操作或者物理机器出现故障都会导致虚拟机不可用&#xff0c;虚拟机中的数据丢失。 虚拟化数据恢复环境&#xff1a; 有一台虚拟机是由物理机迁移到ESXI上面的&a…

pikachu靶场(xss通关教程)

&#xff08;注&#xff1a;若复制注入代码攻击无效&#xff0c;请手动输入注入语句&#xff0c;在英文输入法下&#xff09; 反射型xss(get型) 1.打开网站 发现有个框&#xff0c;然后我们在框中输入一个“1”进行测试&#xff0c; 可以看到提交的数据在url处有显示&#xf…

Debian Linux 下给Nginx 1.26.0 编译增加Brotli算法支持

明月发现参考【给Nginx添加谷歌Brotli压缩算法支持】一文给出的方法&#xff0c;在Debian Linux 12.5下就一直编译失败&#xff0c;主要的错误是因为文件缺失&#xff0c;在专门又安装了apt-get install libbrotli-dev的依赖库后依然会因为文件缺失无法编译完成&#xff0c;就这…

ERP与MES与WMS集成

WMS储位管理 WMS与MES集成 (一) 打通追溯链 在拣货时&#xff0c;将配料标签与供应商的物料标签进行关联。通过配料标签达到精确追溯及防错目的。针对模糊查询&#xff0c;将工单与物料的供应商信息、仓库流转信息进行关联。 (二) WMS入库 成品(半成品)下线后&#xff0c;M…

draw.text((left, top - 15), text,font=font, fill=“green”)

这是一个Python PIL库中的方法&#xff0c;用于在图片上绘制文本。具体来说&#xff0c;它可以在指定的位置绘制指定的文本&#xff0c;并使用指定的字体、颜色等参数进行渲染。其中&#xff0c;left和top是文本绘制的左上角坐标&#xff0c;text是要绘制的文本内容&#xff0c…

齿轮滚刀刃口钝化技术简介

介绍 在滚刀的使用中发现&#xff0c;进口滚刀和国产滚刀在加工质量和寿命方面存在显著差异。经过多次比较得知&#xff0c;滚刀的使用寿命可以达到国产滚刀的两倍以上&#xff0c;而进口滚刀返回原厂磨削后的使用寿命约为新刀具的90% &#xff0c;但同样经过国内厂家磨削后&a…

【C语言项目】贪吃蛇(下)

个人主页~ 源码在Gitee仓库~ 上一篇贪吃蛇&#xff08;上&#xff09;~ 贪吃蛇 四、核心的实现游戏测试1、GameStart&#xff08;1&#xff09;控制台窗口大小和名字设置&#xff08;2&#xff09;光标隐藏&#xff08;3&#xff09;打印欢迎界面&#xff08;4&#xff09;创建…

azkaban-tools 项目介绍

本文背景 应一个用户的好心和好奇心&#xff0c;在最近水深火热的百忙之中抽时间写完了一个简短的项目介绍&#xff0c;其实就是几个azkaban的批量操作脚本&#xff0c;但在大数据集群的“运维生涯”中&#xff0c;还是帮了自己不少忙&#xff0c;也算是为了它做一个简单的回顾…

Java | Leetcode Java题解之第85题最大矩形

题目&#xff1a; 题解&#xff1a; class Solution {public int maximalRectangle(char[][] matrix) {int m matrix.length;if (m 0) {return 0;}int n matrix[0].length;int[][] left new int[m][n];for (int i 0; i < m; i) {for (int j 0; j < n; j) {if (mat…

Python3 + Appium + 安卓模拟器实现APP自动化测试并生成测试报告

这篇文章主要介绍了Python3 Appium 安卓模拟器实现APP自动化测试并生成测试报告,本文给大家介绍的非常详细&#xff0c;对大家的学习或工作具有一定的参考借鉴价值&#xff0c;需要的朋友可以参考下 本文主要分为以下几个部分 安装Python3 安装Python3的Appium库 安装Andr…

Mp3tag for Mac:音乐标签,轻松管理

还在为杂乱无章的音乐文件而烦恼吗&#xff1f;Mp3tag for Mac&#xff0c;让您的音乐库焕然一新&#xff01;它支持多种音频格式&#xff0c;批量编辑标签&#xff0c;让音乐管理变得简单高效。同时&#xff0c;自动获取在线数据库的音乐元数据&#xff0c;确保您的音乐库始终…

kafka安装配置及集成springboot

1. 安装 单机安装kafka Kafka对于zookeeper是强依赖&#xff0c;保存kafka相关的节点数据&#xff0c;所以安装Kafka之前必须先安装zookeeper dockerhub网址: https://hub.docker.com Docker安装zookeeper 下载镜像&#xff1a; docker pull zookeeper:3.4.14创建容器 doc…