1. 程式人生 > >程式設計之美---尋找發帖“水王”

程式設計之美---尋找發帖“水王”

問題描述:

Tango是微軟亞洲研究院的一個試驗專案。研究院的員工和實習生們都很喜歡在Tango上面交流灌水。傳說,Tango有一大“水王”,他不但喜歡發貼,還會回覆其他ID發的每個帖子。坊間風聞該“水王”發帖數目超過了帖子總數的一半。如果你有一個當前論壇上所有帖子(包括回帖)的列表,其中帖子作者的ID也在表中,你能快速找出這個傳說中的Tango水王嗎?
轉化:陣列中有一個數字出現的次數超過了陣列長度的一半,找出這個數字。

問題求解:

分析:在這個題目中,有一個電腦科學中很普遍的思想,就是如何把一個問題轉化為規模較小的若干個問題。分治、遞推和貪心等都有這樣的思想。在轉化過程中,如果能保證小的問題跟原問題的解是一致的就成功了。這樣,我們可以通過尋找這樣的方式將小問題轉化為更小的問題。如何將大問題拆成小問題,或者如何大規模的資料降成小規模,而不影響解呢?

如果每次刪除兩個不同的ID,不管刪除的ID是否包含“水王”的ID,在剩下的ID列表中,“水王”ID出現的次數仍然超過總數的一半。想到這一點之後,上述分析的思想已經構築完成,就可以通過不斷重複這個過程,把ID列表中的ID總數降低(轉化為更小的問題),從而得到問題的答案。新的思路,避免了排序這個耗時的步驟,總的時間複雜度只有O(N),且只需要常數的額外記憶體。

進一步考慮:陣列中有個數字出現的次數超過了陣列長度的一半。也就是說,有個數字出現的次數比其他所有數字出現次數的和還要多。
因此我們可以考慮在遍歷陣列的時候儲存兩個值:一個是陣列中的一個數字,一個是次數。當我們遍歷到下一個數字的時候,如果下一個數字和我們之前儲存的數字相同,則次數加1。 如果下一個數字和我們之前儲存的數字不同,則次數減1。如果次數為零,我們需要儲存下一個數字,並把次數重新設為1。

例一:5,5,5,5,1
不同的相消,相同的累積。遍歷到第四個數字時,candidate 是5, nTimes 是4; 遍歷到第五個數字時,candidate 是5, nTimes 是3; nTimes不為0,那麼candidate就是超過半數的。

例二:0,1,2,1,1
開始時,儲存candidate是數字0,ntimes為1,遍歷到數字1後,與數字0不同,則ntime減1變為零,;接下來,遍歷到數字2,2與1不同,candidate儲存數字2,且ntimes重新設為1;繼續遍歷到第4個數字1時,與2不同,ntimes減一為零,同時candidate儲存為1;最終遍歷到最後一個數字還是1,與我們之前candidate儲存的數字1相同,ntime加一為1。最後返回的是之前儲存的candidate為1。
步驟如下:
i=0,candidate=0,nTimes=1;
i=1,a[1]=1 != candidate,nTimes–,=0;
i=2,candidate=2,nTimes=1;
i=3,a[3] != candidate,nTimes–,=0;
i=4,candidate=1,nTimes=1;
如果是0,1,2,1,1,1的話,那麼i=5,a[5]=1=candidate,nTimes++,=2;……

#include <iostream>
using namespace std;
typedef int Type;

Type Find(Type* ID, int N)
{//ID代表陣列,N代表陣列長度
    Type candidate;
    int nTimes, i;
    for(i=nTimes=0; i<N; i++)
    {
        if(nTimes == 0)
        {
            candidate = ID[i], nTimes = 1;
        }
        else
        {
            if(candidate == ID[i])
            {
                nTimes++;
            }
            else
            {
                nTimes--;
            }
        }
    }
    return candidate;
}
int main()
{
    Type a[] = {2, 3, 4, 5, 3, 6, 3, 3, 4, 3, 6, 3};
    cout<<"The candidate is : "<<Find(a, 12)<<endl;
    return 0;
}

執行結果:

The candidate is : 3

能夠利用該解法的關鍵在於“水王”發帖數目超過了帖子總數的一半,但是當剛好是一半的時候便不適用。此時可以做如下改進:

#include <iostream>
using namespace std;
typedef int Type;

Type Find(Type* ID, int N)
{//ID代表陣列,N代表陣列長度
    Type candidate;
    int nTimes, i;
    for(i=nTimes=0; i<N; i++)
    {
        if(nTimes == 0)
        {
            candidate = ID[i], nTimes = 1;
        }
        else
        {
            if(candidate == ID[i])
            {
                nTimes++;
            }
            else
            {
                nTimes--;
            }
        }
    }
    //處理不存在出現次數超過一半的情況
    int cnt=0;
    for(int j=0;j<N;j++)
    {
        if(ID[j] == candidate) cnt++;
    }
    if(cnt > N/2) return candidate;
    else return 0;
}
int main()
{
    //Type a[] = {2, 3, 4, 5, 3, 6, 3, 3, 4, 3, 6, 3};
    Type a[] = {2, 3, 4, 3};
    cout<<"The candidate is : "<<Find(a, 4)<<endl;
    return 0;
}