面試題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

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM