912. 排序数组
上一次我们做这道题时用的是数组划分三块的思想搭配随机选择基准元素的⽅法。
随机选择一个数,以这个数key为基准划分数组,小于key的数在左边,大于key的数在右边。再把被划分的两部份再找key值划分,直到只剩1或者0个元素返回。
遍历完最后一层该数组就排序完成
接下来我们采用归并的思想来做。
1.以中间点mid把数组分为两部份
2.一直划分直到 到最后一层只剩一个元素或者没有元素返回
3.之下而上返回时,要对左右两部份数组进行排序。借助一个临时数组 两个指针分别指向两个数组,由小到大排,先把小的放里面。再把临时数组排好的内容写回原数组。
4.以此类推,直到返回最上层,合并完左右两个有序数组,排序完成。
归并排序递归实现过程就像二叉树的后续遍历。
class Solution {vector<int> tmp;
public:vector<int> sortArray(vector<int>& nums) {tmp.resize(nums.size());mergeSort(nums,0,nums.size()-1);return tmp;}void mergeSort(vector<int>&nums,int left,int right){if(left>=right) return;//1.选择中间点划分区域int mid=(left+right)>>1;//2.把左右区间排序mergeSort(nums,left,mid);mergeSort(nums,mid+1,right);//3.合并两个有序数组int cur1=left,cur2=mid+1,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++];//将tmp中排好序的数字写回numsfor(int i=left;i<=right;i++)nums[i]=tmp[i-left];}
};
1.借助临时数组时,最好把它定义成全局变量,提前申请好空间,提高效率。
2.合并左右两数组时,可能有一方数组提前结束,后面还需要把另一个数组的剩余部分元素继续合并。
3.int mid=(left+right)>>1表示/2。如果数据溢出可以mid=left+(right-left)/2
LCR 170. 交易逆序对的总数
逆序对,第一个数>第二个数 且第一个数在数组的位置要在第二个数前面。
方法:归并
1.从一个数组中找逆序对,我们把该数组分为两半,左边部分和右半部分。
那逆序对的个数=左半部分逆序对的个数+右半部分逆序对的个数+左边取一个数和右边取一个数组成的逆序对的个数。
2.如果我们再计算完左右部分内逆序对的个数后,对左右部分内进行排序,一左一右的逆序对个数会改变吗?不会,因为左右两个数的相对位置没有改变,左半部分数仍在右半部分数的前面。一左一右挑完逆序对后,排序也不影响。
所以 总数=左半部分+排序 +右半部分+排序 +一左一右+排序
3.利用归并。算一个数组的逆序对个数,先把它分为左右两部份。求左部分的个数,是不是也可以把它再分为左右两部分,直到分到只剩1 0个元素 逆序对个数就为0。这和归并排序的规律是不是很像呢?
所以在递归过程中我们就可以把左右两部分的个数算出来。想一想,如果第一次原数组,分为左右部分,左部分的个数=它的左部分+它的右部分+它的一左一右。因此,我们只要算出一左一右的个数,第一层左部分 右部分的个数在递归中就可以算出来。
4.怎么算一左一右的个数?
首先为什么要排序呢?因为,当左右部分内排完序 左右部分都是升序/降序,进行归并时 当我们固定cur2 只要算出在左部分有多少个大于cur2的数就可以了。
策略一:找出该数前,有多少个数比我大
先来看升序时归并的代码,我们先规定cur2不动,找左部分大于cur2的个数。cur1++直到cur1>cur2,个数就为mid-cur1+1 (cur1右边),最后cur2++ 继续找。
1.nums[cur1]<=nums[cur2] cur1++;
2.nums[cur1]>nums[cur2] re+=mid-cur1+1; cur2++;
但我们反过来,先固定cur1不动,让cur2找比cur1小的个数。cur2++直到cur2>cur1,个数为cur2-(mid+1)+1 (cur2左边) ,最后cur1++ cur2++继续找。但cur2向右移,等再找到时,cur2左边的个数就包含的上一次找到的个数,就会重复。所以 升序时要先固定cur2
while(cur1<=mid&&cur2<=right){if(nums[cur1]<=nums[cur2]) tmp[i++]=nums[cur1++];else {re+=mid-cur1+1;//cur1右边个数tmp[i++]=nums[cur2++];}}
策略二:当前元素的后面,有多少个元素比我小
降序也可以完成,不过我们先规定cur1不动,找右部分小于cur1的个数。cur2++直到cur2<cur1,个数就为right-cur2+1 (cur2右边),最后cur1++ 换下一个位置 继续找。
1.nums[cur1]<=nums[cur2] cur2++;
2.nums[cur1]>nums[cur2] re+=right-cur2+1; cur1++;
while(cur1<=mid&&cur2<=right){if(nums[cur1]<=nums[cur2]) tmp[i++]=nums[cur2++];else {re+=right-cur2+1;//cur2右边的个数tmp[i++]=nums[cur1++];}}
class Solution {vector<int> tmp;
public:int reversePairs(vector<int>& record) {tmp.resize(record.size());return Sort(record,0,record.size()-1);}int Sort(vector<int>&nums,int left,int right){int re=0;if(left>=right) return 0;//1.找中间数,把数组分为两半int mid=(left+right)>>1;//2.左边的个数+排序 右边的个数+排序re+=Sort(nums,left,mid);re+=Sort(nums,mid+1,right);//3.一左一右的个数 (升序)int cur1=left,cur2=mid+1,i=0;while(cur1<=mid&&cur2<=right){if(nums[cur1]<=nums[cur2]) tmp[i++]=nums[cur1++];else {re+=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 re;}
};
315. 计算右侧小于当前元素的个数
这道题也是算逆序对的个数,不过 上一道是算逆序对总数。这道是算在该下标下后面的数中可以组成逆序对的个数。
在排序中会把元素的下标改变,所以我们要有一个数组index记录元素在原数组的下标,并在排序过程中随着的元素同步改变 保证能找到该元素在原数组的下标。
我们以降序为基础,cur1固定 cur2++找cur2<cur1,right-cur2+1(cur2右边)的数就是可以和cur1组成逆序对的数,再根据index映射到cur1在原数组的下标。
class Solution {vector<int> tmp;vector<int> tmp2;vector<int> index;vector<int> re;
public:vector<int> countSmaller(vector<int>& nums) {int n=nums.size();re.resize(n);tmp.resize(n);tmp2.resize(n);index.resize(n);for(int i=0;i<n;i++) index[i]=i;sort(nums,0,n-1);return re;}void sort(vector<int>&nums,int left,int right){if(left>=right) return;int mid=(right-left)/2+left;sort(nums,left,mid);sort(nums,mid+1,right);int cur1=left,cur2=mid+1,i=0;while(cur1<=mid&&cur2<=right){if(nums[cur1]<=nums[cur2]){tmp2[i]=index[cur2];tmp[i++]=index[cur2++];}else {re[index[cur1]]+=right-cur2+1;tmp2[i]=index[cur1];tmp[i++]=nums[cur1++];}}while(cur1<=mid){tmp2[i]=index[cur1];tmp[i++]=nums[cur1++];}while(cur2<=right){tmp2[i]=index[cur2];tmp[i++]=nums[cur2++];}for(int i=left;i<=right;i++){nums[i]=tmp[i-left];index[i]=tmp2[i-left];}}
};
1.数组降序排序
2.index数组建立元素和它在原数组的下标的映射
3.index数组中的对应元素要在nums元素改变位置时同步改变
4.re[index[cur1]]+=right-cur2+1;
index[cur1]因为是固定cur1不动,进行映射时要找cur1
+= 在向上递归时 不断在合并的右数组中找逆序对
493. 翻转对
这道和找逆序对总个数的题不同的是,[n,m]前面n较大的值要>后面n小的值的2倍。
原本是n>m就可以了,这会导致无法在排序时一起找出翻转对。
1.我们要在排序前,利用单调性先找出翻转对的个数
2.再进行排序并合并 进行递归
依旧是把数组以中间点mid分为左右两部份,cur1 cur2分别指向左右部分。
1.降序 cur1固定 cur2++ 找cur2*2<cur1的点 re+=rgiht-cur2+1(cur2右边)
2.升序 cur2固定 cur1++ 找cur1/2>cur2的点 re+=mid-cur1+1(cur1右边)
降序 注意nums[cur2]*2时会出现整数溢出,可以选择用中间变量long long存取后再比较。
或者判断条件nums[cur1]<=nums[cur2]*2两边都除2.0 -> nums[cur1]/2.0<=nums[cur2]
为什么除2.0 而不是2?因为除整型2会除不尽
class Solution {vector<int> tmp;
public:int reversePairs(vector<int>& nums) {int n=nums.size();tmp.resize(n);return sort(nums,0,nums.size()-1);}int sort(vector<int>&nums,int left,int right){if(left>=right) return 0;int re=0;int mid=(left+right)>>1;re+=sort(nums,left,mid);re+=sort(nums,mid+1,right);int cur1=left,cur2=mid+1,i=0;while(cur1<=mid&&cur2<=right){long long c2=nums[cur2];c2*=2;if(nums[cur1]<=c2){cur2++;}else{re+=right-cur2+1;cur1++;}} cur1=left,cur2=mid+1,i=0;while(cur1<=mid&&cur2<=right){if(nums[cur1]<=nums[cur2]){tmp[i++]=nums[cur2++];}else{tmp[i++]=nums[cur1++];}}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 re;}
};
升序 改变cur1 cur2顺序就可以
class Solution {vector<int> tmp;
public:int reversePairs(vector<int>& nums) {int n=nums.size();tmp.resize(n);return sort(nums,0,nums.size()-1);}int sort(vector<int>&nums,int left,int right){if(left>=right) return 0;int re=0;int mid=(left+right)>>1;re+=sort(nums,left,mid);re+=sort(nums,mid+1,right);int cur1=left,cur2=mid+1,i=0;while(cur1<=mid&&cur2<=right){if(nums[cur1]/2.0<=nums[cur2]){cur1++;}else{re+=mid-cur1+1;cur2++;}} cur1=left,cur2=mid+1,i=0;while(cur1<=mid&&cur2<=right){if(nums[cur1]<=nums[cur2]){tmp[i++]=nums[cur1++];}else{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 re;}
};