力扣刷题笔记:排序(12)

常见三大排序

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());
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值