一、颜色划分
. - 力扣(LeetCode)
class Solution {
public:
void sortColors(vector<int>& nums)
{
//三路划分的思想
int n=nums.size();
int left=-1, right=n,cur=0;
while(cur<right)
{
if(nums[cur]==0) swap(nums[++left],nums[cur++]);
else if(nums[cur]==1) ++cur;
else swap(nums[--right],nums[cur]);
}
}
};
二、快排优化(三路划分+随机key)
. - 力扣(LeetCode)
class Solution {
public:
vector<int> sortArray(vector<int>& nums)
{
//三路划分+随机取key
srand((unsigned int)time(NULL));//时间种子
qsort(nums,0,nums.size()-1);
return nums;
}
void qsort(vector<int>& nums,int begin,int end)
{
if(begin>=end) return;
int key=nums[rand()%(end-begin+1)+begin];
int left=begin-1,right=end+1,cur=begin;
while(cur<right)
{
if(nums[cur]<key) swap(nums[++left],nums[cur++]);
else if(nums[cur]==key) ++cur;
else swap(nums[--right],nums[cur]);
}
qsort(nums,begin,left);
qsort(nums,right,end);
}
};
三、数组中的第k个最大元素(快速选择算法)
. - 力扣(LeetCode)
class Solution {
public:
int findKthLargest(vector<int>& nums, int k)
{
//第k大 堆排
//第k小
//前k大 //快速选择算法
//前k小
//三路划分
srand(time(NULL));//时间种子
return qsort(nums,0,nums.size()-1,k);
}
int qsort(vector<int>& nums,int begin,int end,int k)
{
if(begin>=end) return nums[begin];
//1随机选择基准元素
int key=nums[rand()%(end-begin+1)+begin];
//2根据基准元素将数组分三块
int left=begin-1,right=end+1,cur=begin;
while(cur<right)
{
if(nums[cur]<key) swap(nums[++left],nums[cur++]);
else if(nums[cur]==key) ++cur;
else swap(nums[--right],nums[cur]);
}
//3.开始分情况讨论
int c=end-right+1;
int b=right-1-(left+1)+1;
if(c>=k) return qsort(nums,right,end,k);
else if(b+c>=k) return key;
else return qsort(nums,begin,left,k-b-c);
}
};
四、库存管理(Top-k)
. - 力扣(LeetCode)
class Solution {
public:
//最小的k个数
vector<int> inventoryManagement(vector<int>& nums, int k)
{
srand(time(NULL));//时间种子
qsort(nums,0,nums.size()-1,k);
return {nums.begin(),nums.begin()+k};
}
void qsort(vector<int>& nums,int begin,int end,int k)
{
if(begin>=end) return;
int key=nums[rand()%(end-begin+1)+begin];
int left=begin-1,right=end+1,cur=begin;
while(cur<right)
{
if(nums[cur]<key) swap(nums[++left],nums[cur++]);
else if(nums[cur]==key) ++cur;
else swap(nums[--right],nums[cur]);
}
int a=left+1,b=right-left+1;
if(a>k) qsort(nums,begin,left,k);
else if(a+b>=k) return;
else qsort(nums,right,end,k-a-b);
}
};
五、归并排序
. - 力扣(LeetCode)
class Solution {
public:
//需要一个辅助数组
vector<int> temp;//辅助数组
vector<int> sortArray(vector<int>& nums)
{
int n=nums.size();
temp.resize(n);
MergeSort(nums,0,n-1);
return nums;
}
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 i=left;//帮助还原
int cur1=left,cur2=mid+1;
while(cur1<=mid&&cur2<=right)
temp[i++]=nums[cur1]<nums[cur2]?nums[cur1++]:nums[cur2++];
//4、处理没有归并的区间
while(cur1<=mid) temp[i++]=nums[cur1++];
while(cur2<=right) temp[i++]=nums[cur2++];
//5、还原
for(int j=left;j<=right;++j) nums[j]=temp[j];
}
};
六、交易逆序对的总数
. - 力扣(LeetCode)
升序版本:
class Solution {
public:
vector<int> temp;//标记数组
//1、升序,有多少个数比当前位置大
int reversePairs(vector<int>& nums)
{
int n=nums.size();
temp.resize(n);
return MergeSort(nums,0,n-1);
}
int MergeSort(vector<int>& nums,int left,int right)
{
if(left>=right) return 0;
int mid=(right+left)>>1;
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]) temp[i++]=nums[cur1++];
else
{
ret+=mid-cur1+1;
temp[i++]=nums[cur2++];
}
}
while(cur1<=mid) temp[i++]=nums[cur1++];
while(cur2<=right) temp[i++]=nums[cur2++];
//开始还原
for(int j=left;j<=right;++j) nums[j]=temp[j-left];
return ret;
}
};
降序版本:
class Solution {
public:
vector<int> temp;//标记数组
//1、升序,前面有多少个数比当前位置大
//2、降序,后面有多少数比我当前位置小
int reversePairs(vector<int>& nums)
{
int n=nums.size();
temp.resize(n);
return MergeSort(nums,0,n-1);
}
int MergeSort(vector<int>& nums,int left,int right)
{
if(left>=right) return 0;
int mid=(right+left)>>1;
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]) temp[i++]=nums[cur2++];
else
{
ret+=right-cur2+1;//说明我cur1后面的位置都比你大
temp[i++]=nums[cur1++];
}
}
while(cur1<=mid) temp[i++]=nums[cur1++];
while(cur2<=right) temp[i++]=nums[cur2++];
//开始还原
for(int j=left;j<=right;++j) nums[j]=temp[j-left];
return ret;
}
};
七、计算右侧小于当前元素的个数
. - 力扣(LeetCode)
class Solution {
public:
vector<int> ret;//记录返回的结果
vector<int> index;//建立一个下标数组和nums数据绑定
vector<int> numstemp;
vector<int> indextemp;
vector<int> countSmaller(vector<int>& nums)
{
int n=nums.size();
ret.resize(n);
index.resize(n);
numstemp.resize(n);
indextemp.resize(n);
//初始化index数组
for(int i=0;i<n;++i) index[i]=i;//存当前位置对应的下标
Mersort(nums,0,n-1);
return ret;
}
void Mersort(vector<int>& nums,int left,int right)
{
if(left>=right) return;
int mid=(left+right)>>1;
Mersort(nums,left,mid);
Mersort(nums,mid+1,right);
//开始归并 升序,有多少个数比我当前的位置小
int cur1=left,cur2=mid+1,i=left;
while(cur1<=mid&&cur2<=right)
{
if(nums[cur1]>nums[cur2])
{
ret[index[cur1]]+=right-cur2+1;
numstemp[i]=nums[cur1];
indextemp[i++]=index[cur1++];
}
else
{
numstemp[i]=nums[cur2];
indextemp[i++]=index[cur2++];
}
}
//填没有进去的区间
while(cur1<=mid)
{
numstemp[i]=nums[cur1];
indextemp[i++]=index[cur1++];
}
while(cur2<=right)
{
numstemp[i]=nums[cur2];
indextemp[i++]=index[cur2++];
}
//还原
for(int j=left;j<=right;++j)
{
nums[j]=numstemp[j];
index[j]= indextemp[j];
}
}
};
八、翻转对
. - 力扣(LeetCode)
升序版本:前面有多少元素的一半比我大(升序) 固定cur2 找cur1
class Solution {
public:
vector<int> temp;//标记数组
//1、升序,前面有多少个数比当前位置大
//2、降序,后面有多少数比我当前位置小
int reversePairs(vector<int>& nums)
{
int n=nums.size();
temp.resize(n);
return MergeSort(nums,0,n-1);
}
int MergeSort(vector<int>& nums,int left,int right)
{
if(left>=right) return 0;
int mid=(right+left)>>1;
int ret=0;
ret+=MergeSort(nums,left,mid);
ret+=MergeSort(nums,mid+1,right);
//归并之前先统计翻转对
int cur1=left,cur2=mid+1;
//升序 锁定cur2,然后统计cur1的一半是否大于cur2
while(cur2<=right)
{
while(cur1<=mid&&nums[cur2]>=nums[cur1]/2.0) ++cur1;
//此时cur2的位置和cur2后面的位置都符合条件
ret+=mid-cur1+1;
++cur2;
}
//开始进行归并
cur1=left,cur2=mid+1;//恢复cur1和cur2为初始位置
int i=left;
while(cur1<=mid&&cur2<=right)
temp[i++]=nums[cur1]<nums[cur2]?nums[cur1++]:nums[cur2++];//降序
while(cur1<=mid) temp[i++]=nums[cur1++];
while(cur2<=right) temp[i++]=nums[cur2++];
//开始还原
for(int j=left;j<=right;++j) nums[j]=temp[j];
return ret;
}
};
降序版本:后面有多少元素的两倍比我小(降序) 固定cur1 找cur2
class Solution {
public:
vector<int> temp;//标记数组
//1、升序,前面有多少个数比当前位置大
//2、降序,后面有多少数比我当前位置小
int reversePairs(vector<int>& nums)
{
int n=nums.size();
temp.resize(n);
return MergeSort(nums,0,n-1);
}
int MergeSort(vector<int>& nums,int left,int right)
{
if(left>=right) return 0;
int mid=(right+left)>>1;
int ret=0;
ret+=MergeSort(nums,left,mid);
ret+=MergeSort(nums,mid+1,right);
//归并之前先统计翻转对
int cur1=left,cur2=mid+1;
//降序 锁定cur1,然后统计cur2位置的2倍是否小于cur1
while(cur1<=mid)
{
while(cur2<=right&&nums[cur2]>=nums[cur1]/2.0) ++cur2;
//此时cur2的位置和cur2后面的位置都符合条件
ret+=right-cur2+1;
++cur1;
}
//开始进行归并
cur1=left,cur2=mid+1;//恢复cur1和cur2为初始位置
int i=0;
while(cur1<=mid&&cur2<=right)
temp[i++]=nums[cur1]>nums[cur2]?nums[cur1++]:nums[cur2++];//降序
while(cur1<=mid) temp[i++]=nums[cur1++];
while(cur2<=right) temp[i++]=nums[cur2++];
//开始还原
for(int j=left;j<=right;++j) nums[j]=temp[j-left];
return ret;
}
};
九、区间和的个数
. - 力扣(LeetCode)
class Solution {
public:
vector<long> dp;
vector<long> temp;
int lower, upper;
int countRangeSum(vector<int>& nums, int _lower, int _upper)
{
//设置一个前缀和数组dp 则问题等价于求所有下标为(i,j)满足dp[j]-dp[i]属于lower和upper区间
//此时是有左右两段的,左右两段是分别有序的,对前缀和数组排序并不会修改数组中元素的值,只是改变了元素位置,如对leftright=35位置的前缀和排序,排序后前缀和35位置的数还是原来35位置的数,只是排列变化了
//排列并不会影响前缀和
lower = _lower;
upper = _upper;
int n = nums.size();
dp.resize(n+1);//补了0减少了边界的处理前缀和 presum[i..j] = presum[j] - presum[i - 1] 所以补了0为了减少边界处理
temp.resize(n+1);
for(int i=1;i<=n;++i) dp[i]=dp[i-1]+nums[i-1];
return MergeSort(0,n);
}
int MergeSort(int left, int right)
{
if (left == right) return 0;
int mid = (left + right) >> 1;
int ret = 0;
ret += MergeSort(left, mid);
ret += MergeSort(mid + 1, right);
//开始找符合条件的的区间 //升序,固定k 然后用两个指针去索引找到符合要求的地方
int k = left, l = mid + 1, r = mid + 1;
while (k <= mid)
{
while (l <= right && dp[l] - dp[k] < lower) ++l;
while (r <= right && dp[r] - dp[k] <= upper) ++r;
ret += (r - l);
++k;
}
//进行合并
int cur1 = left, cur2 = mid + 1, i = left;
while (cur1 <= mid && cur2 <= right)
temp[i++] = dp[cur1] < dp[cur2] ? dp[cur1++] : dp[cur2++];
//4、处理没有归并的区间
while (cur1 <= mid) temp[i++] = dp[cur1++];
while (cur2 <= right) temp[i++] = dp[cur2++];
//5、还原
for (int j = left; j <= right; ++j) dp[j] = temp[j];
return ret;
}
};
十,总结
分治思想的典型应用就是快速排序和归并排序
1,快速排序本身相当于一个前序遍历,最好的时间复杂度是NlogN 最差的时间复杂度是N^2 ,最坏的情况是出现在(1)以最左侧或最右侧为基准值的时候,凑巧又接近有序(2)大量重复元素。为了解决这个问题衍生出了优化思路:三组划分+随机取key。并且这种方式还可以解决top-k问题,并且时间复杂度是o(N)比堆排序还优秀,我们称之为快速选择算法。
2,归并排序的本质就是将问题划分成无数个合并两个有序数组的子问题。是一个典型的后序遍历,时间复杂度是NlogN.我们发现他有一个特点就是:在归并之前,两个数组是有序的,这个时候我们可以利用他的单调性去做文章。博主总结出来这类的做题思路大概就是,当这个题目涉及到数组种前面的元素和后面的元素存在某种数值上的大小关系(这样才会和单调性建立联系)时,就可以利用归并排序去帮助我们解决。具体需要升序还是降序需要根据题目而定。