• 面试题36:数组中的逆序对


    题目:在数组中的两个数字如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。例如,有一个数组为Array[0..n] 其中有元素a[i],a[j].如果 当i<j时,a[i]>a[j],那么我们就称(a[i],a[j])为一个逆序对。在数组{7,5,6,4}中一共存在5对逆序对,分别是(7,6),(7,5),(7,4),(6,4),(5,4)。

    参考文献

    排序算法汇总->归并排序

    解题思路

    看到这样的题目,最简单的想法就是遍历每一个元素,让其与后面的元素对比,如果大于则count++,但是这样的时间复杂度是o(n2)。这题有更好的解决方法,时间复杂度只需要o(nlogn)。其实这道题目的思路跟归并排序差不多,求逆序对的过程就是一个求归并排序的过程,在求出逆序对以后,原数组变得有序,是通过归并排序得到的。

    (1)总体的意思就是将数组分成两段,首先求段内的逆序对数量,比如下面两段代码就是求左右两端数组段内的逆序对数量

    inversions+=InversePairsCore(arry,start,mid,temp);//找左半段的逆序对数目
    inversions+=InversePairsCore(arry,mid+1,end,temp);//找右半段的逆序对数目

    (2)然后求段间的逆序对数量,如下面的代码

    inversions+=MergeArray(arry,start,mid,end,temp);//在找完左右半段逆序对以后两段数组有序,然后找两段之间的逆序对。最小的逆序段只有一个元素。

    (3)然后在求段间逆序对的时候,我们分为arry[start...mid]和arry[mid+1...end],然后设置两个指针ij分别指向两段数组的末尾元素,也就是i=mid,j=end。然后比较arry[i]和arry[j],

    1. 如果arry[i]>arry[j],因为两段数组都是有序的,所以arry[i]>arry[mid+1...j],这些都是逆序对,我们统计出的逆序对为j-(mid+1)+1=j-mid。并且将大数arry[i]放入临时数组temp[]当中,i往前移动
    2. 如果arry[i]<arry[j],则将大数arry[j]放入temp[]中,j往前移。

    完整实现代码

    View Code
    #include<iostream>
    #include<stdlib.h>
    using namespace std;
    
    void printArray(int arry[],int len)
    {
        for(int i=0;i<len;i++)
            cout<<arry[i]<<" ";
        cout<<endl;
    }
    int MergeArray(int arry[],int start,int mid,int end,int temp[])//数组的归并操作
    {
        //int leftLen=mid-start+1;//arry[start...mid]左半段长度
        //int rightLlen=end-mid;//arry[mid+1...end]右半段长度
    
        int i=mid;
        int j=end;
        int k=0;//临时数组末尾坐标
        int count=0;
        //设定两个指针ij分别指向两段有序数组的头元素,将小的那一个放入到临时数组中去。
        while(i>=start&&j>mid)
        {
            if(arry[i]>arry[j])
            {
                temp[k++]=arry[i--];//从临时数组的最后一个位置开始排序
                count+=j-mid;//因为arry[mid+1...j...end]是有序的,如果arry[i]>arry[j],那么也大于arry[j]之前的元素,从a[mid+1...j]一共有j-(mid+1)+1=j-mid
                
            }
            else
            {
                temp[k++]=arry[j--];
            }
        }
        cout<<"调用MergeArray时的count:"<<count<<endl;
        while(i>=start)//表示前半段数组中还有元素未放入临时数组
        {
            temp[k++]=arry[i--];
        }
    
        while(j>mid)
        {
            temp[k++]=arry[j--];
        }
    
        //将临时数组中的元素写回到原数组当中去。
        for(i=0;i<k;i++)
            arry[end-i]=temp[i];
    
        printArray(arry,8);//输出进过一次归并以后的数组,用于理解整体过程
        return count;
    
    }
    
    int InversePairsCore(int arry[],int start,int end,int temp[])
    {
        int inversions = 0;  
        if(start<end)
        {
            int mid=(start+end)/2;
            inversions+=InversePairsCore(arry,start,mid,temp);//找左半段的逆序对数目
            inversions+=InversePairsCore(arry,mid+1,end,temp);//找右半段的逆序对数目
            inversions+=MergeArray(arry,start,mid,end,temp);//在找完左右半段逆序对以后两段数组有序,然后找两段之间的逆序对。最小的逆序段只有一个元素。
        }    
        return inversions;
    }
    
    
    int InversePairs(int arry[],int len)
    {
        int *temp=new int[len];
        int count=InversePairsCore(arry,0,len-1,temp);
        delete[] temp;
        return count;
    }
    
    void main()
    {
        //int arry[]={7,5,6,4};
        int arry[]={1,3,7,8,2,4,6,5};
        int len=sizeof(arry)/sizeof(int);
        //printArray(arry,len);
        int count=InversePairs(arry,len);
        //printArray(arry,len);
        //cout<<count<<endl;
        system("pause");
    }

     输出结果:

    调用MergeArray时的count:0
    1 3 7 8 2 4 6 5
    调用MergeArray时的count:0
    1 3 7 8 2 4 6 5
    调用MergeArray时的count:0
    1 3 7 8 2 4 6 5
    调用MergeArray时的count:0
    1 3 7 8 2 4 6 5
    调用MergeArray时的count:1//这是因为上面65之间有段内的逆序对
    1 3 7 8 2 4 5 6
    调用MergeArray时的count:0
    1 3 7 8 2 4 5 6
    调用MergeArray时的count:9//这里全部都是段间的逆序对,(3,2),(7,2),(7,4),(7,5),(7,6),(8,2),(8,4),(8,5),(8,6),一共有九个
    1 2 3 4 5 6 7 8
    逆序对数量:10

     

  • 相关阅读:
    望其项背 iOS
    望其项背 iOS
    望其项背 iOS
    望其项背 iOS
    望其项背 iOS
    望其项背 iOS
    望其项背 iOS
    望其项背 iOS
    望其项背 iOS
    望其项背 iOS
  • 原文地址:https://www.cnblogs.com/xwdreamer/p/2721938.html
Copyright © 2020-2023  润新知