球球速刷LC-排序

排序的一般应用题

最大数字
将字符串排序,两两比较,二者组成的数字越大,则对于字符串在前面。

bool cmp(const string& a , const string& b)
{
      return a+b > b+a;
}

class Solution {
public:
    string largestNumber(vector<int> &num) {
       vector<string>numStr;
       for(auto i:num) numStr.push_back(to_string(i));
        
        sort(numStr.begin() , numStr.end(),cmp);
        
        string result;
         for(int i= 0 ;i < numStr.size(); ++i)
        {
            result +=numStr[i];
        }
        if(result[0] == '0') result ="0";
        
        return result;
    }
};

以下两题思路类似。
合并区间
插入区间

引用指数
理解h-index的概念。并排序后利用二分查找找到第一个满足条件的值。

class Solution {
public:
    int hIndex(vector<int>& citations) {
        auto num=citations;
        sort(num.begin(),num.end());
        for(int i=0;i<num.size();++i){
            num[i]-=(num.size()-i);
        }
        
        int target=0;
        int l=0,r=num.size();
        while(l<r){
            int mid=(l+r)>>1;
            if(num[mid]<0){
                l=mid+1;
            }else{
                r=mid;
            }
        }
        return num.size()-l;
    }
};

快速排序

快排是典型的减而治之策略。快排的难点在“分”这个步骤。即将整个数组划分为<=pivot 和>pivot
两大块,并返回pivot的位置。在分的过程中,注意边界问题。
参考以下文章学习快排的细节处理。
快排的写法
使用快排解决以下问题:
题目:排序数组

归并排序

归并排序的策略是分而治之,及将待排序序列一分为二,各自排序后再合并。它与快速排序的区别是,快排是先划分为左右两边,右边一定>=左边,再各自排序。因此最后无需合并。而归并排序再划分时只是简单的划分为两堆,不存在大小关系,所以最后还有合并环节。
经常使用 优先级队列来实现合并环节。
排序链表

    ListNode*MergeTwoList(ListNode*h1,ListNode*h2)
    {
        if(h1 == NULL && h2 == NULL ) return NULL;
        
        ListNode node(0);
        
        auto p1 = h1;
        auto p2 = h2;
        auto p = &node;
        
        auto cmp=[](ListNode*a,ListNode*b){
           return a->val>b->val;//小顶堆
        };
        priority_queue<ListNode*,vector<ListNode*>,decltype(cmp)>q(cmp);
        if(p1) q.push(p1);
        if(p2) q.push(p2);
        while(!q.empty())
        {
           ListNode*top=q.top();
           q.pop();
           p->next=top;
           if(top->next) q.push(top->next);
           p=p->next;            
        }
        return node.next;
    }
    
    ListNode*SortOneList(ListNode*head)
    {
       if(head == NULL || head->next == NULL) return head;
       if(head->next->next==NULL){
          if(head->val<=head->next->val) return head;
          else{
              auto temp = head->next;
              head->next->next=head;
              head->next=NULL;
              return temp;
          }
       }
        
    
        //split to two list
        auto pFirst = head;
        auto pSecond = head;
        
        while(pFirst && pFirst->next && pFirst->next->next)
        {
            pSecond = pSecond->next;
            pFirst = pFirst->next->next;
        }
        
        //only two nodes;
        auto temp = pSecond;
        pSecond = pSecond->next;
        temp->next = NULL;
        
       auto h1=  SortOneList(pSecond);       
       auto h2 = SortOneList(head);
       return  MergeTwoList(h1,h2);
        
    }
        
    ListNode* sortList(ListNode* head) {        
        return SortOneList(head);         
    }

归并环节的应用。
合并K列表

归并排序的一个特点
归并排序在完成“”这个环节后,序列被分为各自有序的左半边{L}集合,和右半边{R}集合。而且所有L集合元素,在原数组中的位置是在R集合的左边。也即是L、R元素之间的相对左右位置尚没有发生改变。利用这个性质,可以结合二分查找求数组中每个元素ai的逆序数个数。(即j>i 且 aj 求逆序数

class Solution{
    //利用归并排序,划分左右两个有序区间,并依次找到左区间元素在右区间的逆序数
    
    int count_less(int left_border,int right_border,int target,vector<pair<int,int>>&nums){
        int l=left_border;
        int r=right_border+1;
        while(l<r){
            int mid=(l+r)>>1;
            if(nums[mid].first<target){
                l=mid+1;
            }else{
                r=mid;
            }
        }
        return l-left_border;
    }
    
    vector<int>res;
    void mergeSort(int lo,int hi ,vector<pair<int,int>>&nums){
        if(lo>=hi) return;
        int mid_sort=(lo+hi)>>1;  //内外有两个mid,注意通过显示命名区分.在遇到容易混淆的变量名时,加强变量名自身含义!!!
        mergeSort(lo,mid_sort,nums);
        mergeSort(mid_sort+1,hi,nums);
        for(int i=lo;i<=mid_sort;++i){
            int a=count_less(mid_sort+1,hi,nums[i].first,nums);
            res[nums[i].second]+=a;
        }
        
        //此时cmp的参数必须为const,养成const &cmp的好习惯!!!
        auto cmp=[](const pair<int,int>&a,const pair<int,int>&b)->bool{
            return a.first<b.first;
        };
    
        inplace_merge(nums.begin()+lo,nums.begin()+mid_sort+1,nums.begin()+hi+1,cmp);
    }
    
    public:
     vector<int> countSmaller(vector<int>& nums) {
         if(nums.empty()) return {};
         if(nums.size()==1) return {0};
         
         vector<pair<int,int>>pairs;
         for(int i=0;i<nums.size();++i){
             pairs.push_back(pair<int,int>(nums[i],i));
         }
         res.resize(nums.size(),0);
         mergeSort(0,nums.size()-1,pairs);
         return res;
     }
};

该题还有一个简答解法就是利用插入排序,则一个元素的逆序数就是其插入排序时的位置。

class Solution1 {
public:
    vector<int> countSmaller(vector<int>& nums) {
        //思路一:从右向左依次将数字插入有序数列中,则插入位置索引即为坐标比起小的元素个数
        vector<int> sortedNums;
        if(nums.empty()) return {};
        
        vector<int>ret(nums.size(),0);
        for(int i=nums.size()-1;i>=0;--i){
           if(sortedNums.empty()){
               sortedNums.push_back(nums[i]);
               ret[i]=0;
           }else{
               //利用二分查找找到sortedNums中第一个>= nums[i]的位置
               int l=0,r=sortedNums.size();
               while(l<r){
                   int mid=(l+r)>>1;
                   if(sortedNums[mid]<nums[i]){
                       l=mid+1;
                   }else{
                       r=mid;
                   }
               }               
               sortedNums.insert(sortedNums.begin()+l,nums[i]);
               ret[i]=l;
           }   
        }
        return ret;
    }
};

桶排序

桶排序是基于非比较的排序算法,其实质是用空间换时间。其效率为O(n);
基本原理:
图解桶排序
非比较排序–桶排序
桶排序的一个工程应用案例
桶排序应用

桶排序应用题目:
题目:最大gap
本题解析在 非比较排序–桶排序

总结:桶排序是典型的利用空间换时间,但是需要已知数据的特性,例如数据应均匀分布在一定范围内。此时利用桶排序可以提高时间效率。

堆排序

即使用优先级队列(二叉堆)来实现排序
题目:数据流的中位数

插入排序

将元素依次插入已经排序好的序列
题目:插入排序排序链表
代码略

排序的稳定性分析

排序的稳定性

你可能感兴趣的:(leetcode)