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

devtools/2024/9/23 6:08:14/

二分力扣题

一:搜索二维矩阵

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.ppmy.cn/devtools/40498.html

相关文章

ARM 交叉编译搭建SSH

一、源码下载 zlib&#xff1a;zlib-1.3.1.tar.xz openssl&#xff1a;openssl-0.9.8d.tar.gz openssh&#xff1a;openssh-4.6p1.tar.gz 二、交叉编译 1、zlib 编译参考这里 2、openssl tar -xf openssl-0.9.8d.tar.gz ./Configure --prefix/opt/ssh/openssl os/compile…

StringJoiner

StringJoiner是Java 8新增的一个API&#xff0c;他是基于StringBuilder实现&#xff0c;用于实现对字符串之间通过分隔符拼接的场景。 描述&#xff1a; 这段代码的功能是使用StringJoiner类将字符串数组中的元素连接成一个字符串&#xff0c;并使用指定的分隔符和前后缀。具…

基于 LlaMA 3 + LangGraph 在windows本地部署大模型 (一)

基于LlaMA 3 LangGraph 在windows本地部署大模型 &#xff08;一&#xff09; RAG 是未来人工智能应用的基石。大家并不是在寻求仅仅产生无意义反应的人工智能。而目标是人工智能能够从特定文档集中检索答案&#xff0c;理解查询的上下文&#xff0c;指导自己搜索其嵌入内容或…

【系统架构师】-案例篇(三)NoSQL与分布式对象调用

1、NoSQL 一个基于Web 2.0的大型社交网络系统。就该系统的数据架构而言&#xff0c;李工决定采用公司熟悉的数据架构&#xff0c;使用通用的商用关系型数据库&#xff0c;系统内部数据采用中央集中方式存储。该系统投入使用后&#xff0c;初期用户数量少&#xff0c;系统运行平…

ExcelVBA在选择区域(有合并)中删除清除空行

【问题】 关于删除空行&#xff0c;以前是用函数来完成工作的&#xff0c; 今天有人提出问题&#xff0c;传来这个文件&#xff0c; 现有数据&#xff0c;1w多行&#xff0c;其中有部分列有不同合并单元格&#xff0c;跨行也不一样。如果要进行筛选删除空行&#xff0c;有一定的…

递归式--三种求解时间复杂度的方法

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、代换法二、递归树法三.主方法总结 前言 学无止境&#xff0c;笔勤不辍。很久没有更新算法专栏了…笔者终于找到时间来更新了。今天&#xff0c;笔者给大家…

【源码+文档+调试教程】基于微信小程序的电子购物系统的设计与实现

摘 要 由于APP软件在开发以及运营上面所需成本较高&#xff0c;而用户手机需要安装各种APP软件&#xff0c;因此占用用户过多的手机存储空间&#xff0c;导致用户手机运行缓慢&#xff0c;体验度比较差&#xff0c;进而导致用户会卸载非必要的APP&#xff0c;倒逼管理者必须改…

thinkphp8 framework和 element plus admin前后端分离系统之PHP安装教程

DIYGW-UI-PHP是一款基于thinkphp8 framework和 element plus admin开发而成的前后端分离系统。目的是结合现有diygw-ui打造一个后台API开发。 实现PHP源码前请先下载小皮面板或者宝塔。 系统已经集成了部分功能 用户管理 后台用户管理部门管理 配置公司的部门结构&#xff0…