常见三大排序
1)快速排序
1、快排随机选取基准值pivot,强转随机时间为unsigned类型作为srand种子。
2、划分将基准值与末尾交换,设基准值为左值l,从左往右判断小于nums[r]则与nums[l]交换
3、交换nums[r]与nums[l],将原基准值换回新基准值位置l,返回l
4、左边left为l,右边right为r,进行第一轮划分后进行左右递归。
快速排序的4种优化
1、随机选取基准值或使用头尾中三数取中选取基准值,提升平均性能
2、当范围在5-20小范围间选择插入排序,提升算法效率
3、尾递归优化,将纵向递归调用改为横向迭代循环,减少栈深度避免崩溃
4、聚集元素,将一次划分后与基准值相同的元素移到基准值附近,提升重复数组排序效率
5、使用多线程分组快排,再使用归并排序合并
//进行划分,保证中枢值左侧都比p小,右侧比p大
int partition(vector<int>& nums,int l,int r){
int p = rand()%(r-l+1) + l;
swap(nums[p],nums[r]);
for(int i=l;i<r;i++)
if(nums[i]<nums[r])
swap(nums[l++],nums[i]);
swap(nums[r],nums[l]);
return l;
}
//左边l为left,右边r为right,运行前判断是否l<r
void quicksort(vector<int>& nums,int l,int r){
if(l<r){
//调用划分函数,返回值p为中枢值piviot,再进行中枢两侧递归
int p=partition(nums,l,r);
quicksort(nums,l,p-1);
quicksort(nums,p+1,r);
}
}
vector<int> sortArray(vector<int>& nums) {
srand((unsigned)time(NULL));
quicksort(nums,0,nums.size()-1);
return nums;
}
2)堆排序
1、maxHeap函数作用是建立当前点和之后的最大堆。
2、首先从倒数第二层开始往最高点调maxHeap,建立初始最大堆
3、换下最大值,剩下i-1个建立最大值,依次循环使堆有序
void maxHeap(vector<int>& nums,int now,int len){
while((now*2+1)<=len){
int l=now*2+1,r=now*2+2,large=now;
//必须大于最大值,如果nums[l]>nums[now]],得不到最大值
if(l<=len&&nums[l]>nums[large])
large=l;
if(r<=len&&nums[r]>nums[large])
large=r;
if(large!=now){
swap(nums[now],nums[large]);
now=large;
}else
//如果该点已经最大则退出,不加break会死循环
break;
}
}
vector<int> sortArray(vector<int>& nums) {
int len = nums.size()-1;
for(int i=len/2;i>=0;i--)
maxHeap(nums,i,len);
for(int i=len;i>=1;i--){
swap(nums[0],nums[i]);
//必须从0开始,不能从i,将最大值换上去
maxHeap(nums,0,--len);
}
return nums;
}
3)归并排序
1、对l-r区间分两部分递归排序。
2、递归返回后这两部分都是有序数列,用双指针对这两部分进行逐个比较,存入temp数组。
3、合并比较后剩余数字,最后放入原数组,保证数组在l-r区间为有序,返回递归。
class Solution {
vector<int> tmp;
void mergeSort(vector<int>& nums, int l, int r) {
if (l >= r) return;
int mid = (l + r)/2;
mergeSort(nums, l, mid);
mergeSort(nums, mid + 1, r);
int i = l, j = mid + 1;
int cnt = 0;
while (i <= mid && j <= r) {
if (nums[i] <= nums[j])
tmp[cnt++] = nums[i++];
else
tmp[cnt++] = nums[j++];
}
while (i <= mid)
tmp[cnt++] = nums[i++];
while (j <= r)
tmp[cnt++] = nums[j++];
for (int i = 0; i < r - l + 1; ++i)
nums[i + l] = tmp[i];
}
public:
vector<int> sortArray(vector<int>& nums) {
tmp.resize((int)nums.size(), 0);
mergeSort(nums, 0, (int)nums.size() - 1);
return nums;
}
};
剑指 Offer 51. 数组中的逆序对(归并排序)
双循环扫描会超时,使用归并排序计算逆序对
class Solution {
public:
int mergeSort(vector<int>& nums, vector<int>& tmp, int l, int r) {
if (l >= r)
return 0;
int mid = (l + r) / 2;
int inv_count = mergeSort(nums, tmp, l, mid) + mergeSort(nums, tmp, mid + 1, r);
int i = l, j = mid + 1, pos = l;
while (i <= mid && j <= r) {
if (nums[i] <= nums[j]) {
tmp[pos] = nums[i];
++i;
inv_count += (j - (mid + 1));
}
else {
tmp[pos] = nums[j];
++j;
}
++pos;
}
for (int k = i; k <= mid; ++k) {
tmp[pos++] = nums[k];
inv_count += (j - (mid + 1));
}
for (int k = j; k <= r; ++k)
tmp[pos++] = nums[k];
copy(tmp.begin() + l, tmp.begin() + r + 1, nums.begin() + l);
return inv_count;
}
int reversePairs(vector<int>& nums) {
int n = nums.size();
vector<int> tmp(n);
return mergeSort(nums, tmp, 0, n - 1);
}
};
15. 三数之和(排序+双指针)
1、首先将数组排序,然后固定一个值,剩下两个值用双指针遍历。
2、不包含重复三元组,但元素可能重复,所以first和second不能跟之前的一样。
3、注意second要小于third,并且过程中要大于0
4、本题需要考虑去重,实际上是固定了第二个指针,使用的是单指针
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
int n = nums.size();
sort(nums.begin(), nums.end());
vector<vector<int>> ans;
for(int first=0;first<n;first++)
if (first == 0 || nums[first] != nums[first - 1])//去重{
int third = n - 1;
for (int second = first + 1; second < third; second++)
if (second==first+1||nums[second]!=nums[second-1])//去重{
while (nums[first] + nums[second] + nums[third] > 0 && second<third)
third--;
if (nums[first] + nums[second] + nums[third] == 0 && second<third)
ans.push_back({ nums[first],nums[second],nums[third] });
}
}
return ans;
}
};
16. 最接近的三数之和(排序+双指针)
1、主要利用双指针的思想来实现,首先对数组进行排序,然后固定住第i个元素
2、设置左指针的起始下标为i+1,右指针的起始下标为数组长度长度减1
3、如果nums[i]+nums[left]+nums[right]的值大于target,则令右指针左移一位;如果三数之和小于target,则令左指针右移一位
4、如果三数之和等于target,则直接返回三数之和即可。上述判断规则的前提是左指针下标始终要小于右指针。
5、本题不用考虑去重,直接暴力三重判断
class Solution {
public:
int threeSumClosest(vector<int>& nums, int target)
{
sort(nums.begin(),nums.end());//对数组进行排序
//temp用于存储三数之和与target之间的差,ret用于存储三数之和
int temp=abs(nums[0]+nums[1]+nums[2]-target),ret=nums[0]+nums[1]+nums[2];
for(int i=0;i<nums.size()-1;i++)//遍历nums数组,固定住第i个元素
{
int left=i+1,right=nums.size()-1;//左指针下标从i+1开始,右指针的下标从最后一位开始
while(left<right)//保证左指针下标始终小于右指针下标
{
//先判断如果当前三数之和与target的差比先前的要小,则将前者赋值给后者
if(temp>abs(nums[i]+nums[left]+nums[right]-target))
{
temp=abs(nums[i]+nums[left]+nums[right]-target);
ret=nums[i]+nums[left]+nums[right];
}
//如果当前三数之和大于target
if(nums[i]+nums[left]+nums[right]>target)
{
right--;//令右指针左移一位
continue;//同时直接进行下轮循环
}
//如果当前三数之和小于target
if(nums[i]+nums[left]+nums[right]<target)
{
left++;//令左指针右移一位
continue;//同时直接进行下轮循环
}
//如果当前三数之和等于target
if(nums[i]+nums[left]+nums[right]==target)
{
return nums[i]+nums[left]+nums[right];//直接返回三数之和
}
}
}
return ret;
}
};
18. 四数之和(排序+双指针)
1、使用四个指针(a<b<c<d)。固定最小的a和b在左边,c=b+1,d=_size-1 移动两个指针包夹求解。
2、保存使得nums[a]+nums[b]+nums[c]+nums[d]==target的解。偏大时d左移,偏小时c右移。
3、c和d相遇时,表示以当前的a和b为最小值的解已经全部求得。b++,进入下一轮循环b循环,当b循环结束后。a++,进入下一轮a循环。
4、本题需要考虑去重,可以类比三数之和固定第二个指针来去重
class Solution{
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
sort(nums.begin(),nums.end());
vector<vector<int> > res;
if(nums.size()<4)
return res;
int a,b,c,d,_size=nums.size();
for(a=0;a<=_size-4;a++){
if(a>0&&nums[a]==nums[a-1]) continue; //确保nums[a] 改变了
for(b=a+1;b<=_size-3;b++){
if(b>a+1&&nums[b]==nums[b-1])continue; //确保nums[b] 改变了
c=b+1,d=_size-1;
while(c<d){
if(nums[a]+nums[b]+nums[c]+nums[d]<target)
c++;
else if(nums[a]+nums[b]+nums[c]+nums[d]>target)
d--;
else{
res.push_back({nums[a],nums[b],nums[c],nums[d]});
while(c<d&&nums[c+1]==nums[c]) //确保nums[c] 改变了
c++;
while(c<d&&nums[d-1]==nums[d]) //确保nums[d] 改变了
d--;
c++;
d--;
}
}
}
}
return res;
}
};
56. 合并区间(排序+一次遍历)
按照左区间先排序,然后一次遍历,判断当前序列是否可以加入上一个区间,即判断当前左值是否大于上一个右值
vector<vector<int>> merge(vector<vector<int>>& intervals) {
if (intervals.size() == 0)
return {};
sort(intervals.begin(), intervals.end());
vector<vector<int>> merged;
for (int i = 0; i < intervals.size(); ++i) {
int L = intervals[i][0], R = intervals[i][1];
if (!merged.size() || merged.back()[1] < L)
merged.push_back({L, R});
else
merged.back()[1] = max(merged.back()[1], R);
}
return merged;
}
88. 合并两个有序数组(排序或双指针)
方法1:复制数组后排序
方法2:创建一个新数组,双指针比较存入新数组
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
for (int i = 0; i != n; ++i) {
nums1[m + i] = nums2[i];
}
sort(nums1.begin(), nums1.end());
}
164. 最大间距(基数排序)
class Solution {
public:
int maximumGap(vector<int>& nums) {
int n = nums.size(); //如果数组元素个数小于 2,则返回 0
if(n < 2)
return 0;
vector<int> buf(n); //buf为临时顺组,用于存储每次排完序的数组
int maxVal = *max_element(nums.begin(), nums.end());
int time = maxBit(maxVal); //计算出需要最高位数,即需要排多少次
int dev = 1;
//开始从低位到高位基数排序
for(int i = 0; i < time; i++){
vector<int> count(10); //桶
//统计每个桶中有多少个数
for(int j = 0; j < n; j++){
int digit = (nums[j] / dev) % 10; //digit 为nums[j]的第i位数
count[digit] ++;
}
//此步是将count[j]由原本表示每个桶的数量,变为表示在数组中的索引
for(int j = 1; j < 10; j++){
count[j] += count[j - 1];
}
//此步对nums按照低位大小进行排序,(count[digit] - 1)表示排序后nums[j]应该在的位置
for(int j = n - 1; j >= 0; j--){
int digit = (nums[j] / dev) % 10;
buf[count[digit] - 1] = nums[j];
count[digit] --;
}
//将临时数组拷贝给nums
copy(buf.begin(),buf.end(), nums.begin());
dev *= 10;
}
//找到相邻元素最大差值
int ret = 0;
for (int i = 1; i < n; i++) {
ret = max(ret, nums[i] - nums[i - 1]);
}
return ret;
}
int maxBit(int maxVal){
int p = 10;
int d = 1;
while(maxVal >= p){
p *= 10;
d++;
}
return d;
}
};
215. 数组中的第K个最大元素(快排、堆排)
基于快排的选择方法,不用全部排序完再选择,当枢轴值正好是第k个时就可以返回
class Solution {
public:
int quickSelect(vector<int>& a, int l, int r, int index) {
int q = randomPartition(a, l, r);
if (q == index) {
return a[q];
} else {
return q < index ? quickSelect(a, q + 1, r, index) : quickSelect(a, l, q - 1, index);
}
}
inline int randomPartition(vector<int>& a, int l, int r) {
int i = rand() % (r - l + 1) + l;
swap(a[i], a[r]);
return partition(a, l, r);
}
inline int partition(vector<int>& a, int l, int r) {
int x = a[r], i = l - 1;
for (int j = l; j < r; ++j) {
if (a[j] <= x) {
swap(a[++i], a[j]);
}
}
swap(a[i + 1], a[r]);
return i + 1;
}
int findKthLargest(vector<int>& nums, int k) {
srand(time(0));
return quickSelect(nums, 0, nums.size() - 1, nums.size() - k);
}
};
建立一个大根堆,删除k-1个元素后,第k个元素就是答案
class Solution {
public:
void maxHeapify(vector<int>& a, int i, int heapSize) {
int l = i * 2 + 1, r = i * 2 + 2, largest = i;
if (l < heapSize && a[l] > a[largest])
largest = l;
if (r < heapSize && a[r] > a[largest])
largest = r;
if (largest != i) {
swap(a[i], a[largest]);
maxHeapify(a, largest, heapSize);
}
}
void buildMaxHeap(vector<int>& a, int heapSize) {
for (int i = heapSize / 2; i >= 0; --i)
maxHeapify(a, i, heapSize);
}
int findKthLargest(vector<int>& nums, int k) {
int heapSize = nums.size();
buildMaxHeap(nums, heapSize);
for (int i = nums.size() - 1; i >= nums.size() - k + 1; --i) {
swap(nums[0], nums[i]);
--heapSize;
maxHeapify(nums, 0, heapSize);
}
return nums[0];
}
};
295. 数据流的中位数(双堆排序)
用stl的堆形式的优先队列结构构造一个最大堆一个最小堆,输入数据后插入最大堆,平衡最大最小堆
最后根据两个堆的队头输出结果
class MedianFinder {
public:
priority_queue<int,vector<int>,less<int>> big;
priority_queue<int,vector<int>,greater<int>> small;
int size=0;
MedianFinder() {
}
void addNum(int num) {
big.push(num);
small.push(big.top());
big.pop();
size++;
//平衡两个堆的大小
while(small.size()>big.size())
{
big.push(small.top());
small.pop();
}
}
double findMedian() {
if(size%2==1) return big.top();
else return 0.5*(small.top()+big.top());
}
};