归并排序、求逆序对(offer51)


本题求数组中的逆序对,很自然的想到了两层for循环,然而困难题,一定超时!

在评论区看到了一种利用归并排序“治”的过程,顺带将逆序对求出来了,记录一下,顺便将归并排序和快排做一个比较。

归并排序


正如图所示,归并排序是将原数组不断的划分,然后开始合并,有点二分法的意思;到只剩一个元素的时候,开始合并,将小的有序数组合并为大的有序数组

恰好,这个合并的时候,如果加一个比较的步骤,就可以求出逆序对的个数,例如:
原本的数组是:【7,3,2,6,0,1,5,4】
原本的方法是:将7与余下的数字进行比较,得出逆序次数,余下的元素也是一样的操作;
归并排序的方法:7仍然要跟所有的元素进行比较(效果上面),但是如果它的前面有比7小的数字,这个比较的过程就可以省略(实现上面)

7首先和3比较,得出逆序对1;(2,6)、(0,1)、(5,4)也相应的进行比较;
然后3和2进行比较,3比2大,所以3后面的数字也比2大(小数组已经有序了),因此逆序个数=3及3后面的元素个数;间接得到了7大于2的结果,所以时间复杂度降下来了嘛!

cpp实现

#include <bits/stdc++.h>
using namespace std;
/* 归并排序,一般也可用于求逆序对 */
class Solution 
{
   
private:
    static void MergeSort(int left,int right,vector<int>& nums,vector<int>& tmp)
    {
   
        {
   
        //分
        if(left>=right)//只有一个元素的时候开始返回
            return;
        int m=left+(right-left)/2;
        MergeSort(left,m,nums,tmp);
        MergeSort(m+1,right,nums,tmp);
        //治
        for(int k=left;k<=right;++k)
        {
   
            tmp[k]=nums[k];
        }
        int i=left,j=m+1;
        for(int k=left;k<=right;++k)
        {
   
            //如果i越界了,就将nums[j]添加进tmp
            if(i==m+1)
                nums[k]=tmp[j++];
            //如果j越界了,就将nums[i]添加进tmp
            //nums[i]<=nums[j],将nums[i]添加进tmp
            else if(j==right+1||tmp[i]<=tmp[j])
                nums[k]=tmp[i++];
            else//nums[i]>nums[j],将nums[j]添加进tmp
            {
   
                nums[k]=tmp[j++];
            }    
        }
    }
    }
    static int sort(int left,int right,vector<int>& nums,vector<int>& tmp)
    {
   
        //分
        if(left>=right)//只有一个元素的时候开始返回
            return 0;
        int m=(right+left)/2;
        int res=sort(left,m,nums,tmp)+sort(m+1,right,nums,tmp);
        //治
        for(int k=left;k<=right;++k)
        {
   
            tmp[k]=nums[k];
        }
        int i=left,j=m+1;
        for(int k=left;k<=right;++k)
        {
   
            //如果i越界了,就将nums[j]添加进tmp
            if(i==m+1)
                nums[k]=tmp[j++];
            //如果j越界了,就将nums[i]添加进tmp
            //nums[i]<=nums[j],将nums[i]添加进tmp
            else if(j==right+1||tmp[i]<=tmp[j])
                nums[k]=tmp[i++];
            else//nums[i]>nums[j],将nums[j]添加进tmp,同时逆序对的数量+1;
            {
   
                nums[k]=tmp[j++];
                res+=m-i+1;
            }    
        }
        return res;
    }
public:
    static int reversePairs(vector<int>& nums) 
    {
   
        vector<int> tmp(nums.size());
        return sort(0,nums.size()-1,nums,tmp);
    }
    static void TestMergeSort(vector<int>& nums) 
    {
   
        vector<int> tmp(nums.size());
        MergeSort(0,nums.size()-1,nums,tmp);
    }
};

int main() 
{
   
    vector<int> nums={
   1,3,2,5,6,8};
    vector<int> nums2={
   1,3,2,5,6,8};
    Solution::TestMergeSort(nums);
    int c=Solution::reversePairs(nums2);
    return 0;
}

与快速排序进行比较

快速排序是每次选择一个基准数据,将原数组分为两部分,左边比它小,右边比它大,然后继续将左右两边的数组进行排序;

快速排序利用了问题的信息,如果,每次都是在中间位置进行划分,那么快排的时间复杂度是O(Nlog2N),但如果,每次划分的位置在首部,那么时间复杂度就变成了O(N2),因此是不稳定的;

归并排序,在分的过程中,一直是对半分,没有利用问题的信息;在合的过程中,才开始利用问题的信息;

快速排序cpp实现

void QuickSort2(vector<int>& arr,int left,int right){
   
    //快速排序,每次选取第一个元素作为基准数据,将数组分为两个部分,
    //左边的元素都小于基准数据,右边的元素都大于基准数据。
    if(left>=right)
        return;
    int low=left;
    int high=right;
    while(low<high){
   
        while(low<high&&arr[high]>=arr[left])
            high--;
        while(low<high&&arr[low]<=arr[left])
            low++;
        swap(arr[low],arr[high]);
    }
    swap(arr[left],arr[low]);
    QuickSort2(arr,left,low-1);
    QuickSort2(arr,low+1,right);
}

end!

刷题总结类 文章被收录于专栏

这里记录一些刷题时候的总结思考

全部评论

相关推荐

11-18 15:57
门头沟学院 Java
最终归宿是测开:这个重邮的大佬在重邮很有名的,他就喜欢打92的脸,越有人质疑他,他越觉得爽😂
点赞 评论 收藏
分享
10-17 12:16
同济大学 Java
7182oat:快快放弃了然后发给我,然后让我也泡他七天最后再拒掉,狠狠羞辱他一把😋
点赞 评论 收藏
分享
点赞 收藏 评论
分享
牛客网
牛客企业服务