首先想到的是一個最直接的方法,咱們能夠對全部ID進行排序。而後再掃描一遍排好序的ID列表,統計各個ID出現的次數。若是某個ID出現的次數超過總數的一半,那麼就輸出這個ID。這個算法的時間複雜度爲O(N * log2N + N)。 算法
若是ID列表已是有序的,還須要掃描一遍整個列表來統計各個ID出現的次數嗎? 數組
若是一個ID出現的次數超過總數N的一半。那麼,不管水王的ID是什麼,這個有序的ID列表中的第N/2項(從0開始編號)必定會是這個ID(讀者能夠試着證實一下)。省去從新掃描一遍列表,能夠節省一點算法耗費的時間。若是可以迅速定位到列表的某一項(好比使用數組來存儲列表),除去排序的時間複雜度,後處理須要的時間爲O(1)。 spa
但上面兩種方法都須要先對ID列表進行排序,時間複雜度方面沒有本質的改進。可否避免排序呢? code
如 果每次刪除兩個不一樣的ID(無論是否包含「水王」的ID),那麼,在剩下的ID列表中,「水王」ID出現的次數仍然超過總數的一半。看到這一點以後,就可 以經過不斷重複這個過程,把ID列表中的ID總數下降(轉化爲更小的問題),從而獲得問題的答案。新的思路,避免了排序這個耗時的步驟,總的時間複雜度只 有O(N),且只須要常數的額外內存。僞代碼以下: 排序
代碼清單: 內存
Type Find(Type* ID, int 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; }
在 這個題目中,有一個計算機科學中很廣泛的思想,就是如何把一個問題轉化爲規模較小的若干個問題。分治、遞推和貪心等都是基於這樣的思路。在轉化過程當中,小 的問題跟原問題本質上一致。這樣,咱們能夠經過一樣的方式將小問題轉化爲更小的問題。所以,轉化過程是很重要的。像上面這個題目,咱們保證了問題的解在小 問題中仍然具備與原問題相同的性質:水王的ID在ID列表中的次數超過一半。轉化自己計算的效率越高,轉化以後問題規模縮小得越快,則總體算法的時間複雜 度越低。 class
擴展問題隨着Tango的發展,管理員發現,「超級水王」沒有了。統計結果代表,有3個發帖不少的ID,他們的發帖數目都超過了帖子總數目N的1/4。你能從發帖ID列表中快速找出他們的ID嗎? 效率
參考上面的解法,思路以下:
若是每次刪除四個不一樣的ID(無論是否包含發帖數目超過總數1/4的ID),那麼,在剩下的ID列表中,原先發帖比例大於1/4的ID所佔比例仍然大於1/4。能夠經過不斷重複這個過程,把ID列表中的ID總數下降(轉化爲更小的問題),從而獲得問題的答案。 計算機科學
代碼以下: 擴展
void Find(Type* ID, int N,Type candidate[3]) { Type ID_NULL;//定義一個不存在的ID int nTimes[3], i; nTimes[0]=nTimes[1]=nTimes[2]=0; candidate[0]=candidate[1]=candidate[2]=ID_NULL; for(i = 0; i < N; i++) { if(ID[i]==candidate[0]) { nTimes[0]++; } else if(ID[i]==candidate[1]) { nTimes[1]++; } else if(ID[i]==candidate[2]) { nTimes[2]++; } else if(nTimes[0]==0) { nTimes[0]=1; candidate[0]=ID[i]; } else if(nTimes[1]==0) { nTimes[1]=1; candidate[1]=ID[i]; } else if(nTimes[2]==0) { nTimes[2]=1; candidate[2]=ID[i]; } else { nTimes[0]--; nTimes[1]--; nTimes[2]--; } } return; }