Tango是微軟亞洲研究院的一個試驗項目。研究院的員工和實習生們都很喜歡在Tango上面交流灌水。傳說,Tango有一大水王,他不但喜歡發帖,還會回復其他ID發的每個帖子。坊間風聞改水王發帖數目超過了帖子總數的一半。如果你有一個當前論壇上所有帖子(包括回帖)的列表,其中帖子作者的ID也在表中,你能快速找出這個傳說中的Tango水王嗎?
分析:如果一個ID出現的次數超過總數N的一半,那么先把這些ID排好序,那么這個有序ID列表的第N/2項肯定是水王ID,而且N/2的左邊或者右邊肯定全都是水王的ID。
如果ID沒排好序,那么每次刪除兩個不同的ID(不管是否包含水王的ID),那么兩兩對消之后,剩下的水王ID仍然是超過一半。這樣子就可以通過重復這個過程把ID總數降低(轉化為更小的問題)。到最后,剩下的就是水王的ID。這個方法避免了排序這個耗時的步驟,總的時間復雜度為O(N),而且只需要常數的額外內存。
在這個題目中,就是如何把一個問題轉化為規模較小的若干個問題。分治、遞推和貪心等都是基於這樣的思路。在轉化過程中,小的問題跟原本問題本質上是一致。這樣,我們可以通過同樣的方式將小問題轉化為更小問題。因此,轉化過程是很重要的。轉化本身計算的效率越高,轉化之后問題規模縮小得越快,則整體算法時間復雜度越低。
擴展問題:
隨着Tango的發展,管理員發現,超級水王沒有了。統計結果表明,有3個發帖很多的ID,他們發帖數目都超過了帖子總數N的1/4.你能從發帖ID列表中快速找出他們ID嗎?
數組:6,4,5,4,4,4
開始for數組:
初始化nTimes和candidate為0,
如果nTimes為0,candidate=6,nTimes=1;
第二次由於nTimes不為0,而且candidate=1!=4,那么nTimes-1=0;
這樣子就將第一個6和第二個4對消了。
下一輪由於nTimes已經為0,所以candidate=5,nTimes=1;
到第四個數字4的時候,由於nTimes不為0,而且candidate=5!=4,那么nTimes-1=0,
此時第三個數字5和第四個數字4又對消了。
下一輪從從第五個數字4開始,
由於nTimes=0,所以candidate=5,nTimes=1;
到最后一個數字4的時候,由於(candidate=4)==4,那么nTimes+1=2。
代碼:

1 using System; 2 3 namespace 尋找發帖水王 4 { 5 class Program 6 { 7 static void Main() 8 { 9 int[] ids = InitArray(); 10 int halfId = FindHalfId(ids, ids.Length); 11 PrintIds(halfId); 12 13 //int[] ids = { 4, 8, 4, 5, 6, 6, 5, 7, 6, 5, 4, 5, 4, 6, 9 }; 14 //int[] quarterIds = FindQuarterIds(ids, ids.Length); 15 //PrintIds(quarterIds); 16 Console.ReadLine(); 17 } 18 19 static int[] InitArray() 20 { 21 int[] ids = new int[40]; 22 Random rd = new Random(); 23 int halfId = rd.Next(1111, 1141); 24 25 //創建水王id出現次數 26 for (int i = 0; i < 23; i++) 27 { 28 ids[i] = halfId; 29 } 30 for (int i = 23; i < 40; i++) 31 { 32 ids[i] = rd.Next(1111, 1141); 33 } 34 return ids; 35 } 36 37 static void PrintIds(dynamic t) 38 { 39 if (t is Array) 40 { 41 foreach (var item in t) 42 { 43 Console.WriteLine("發帖超過1\\{0}的水王id:{1}:", t.Length + 1, item); 44 } 45 } 46 else 47 { 48 Console.WriteLine("發帖超過一半的水王Id:{0}", t); 49 } 50 } 51 52 static int FindHalfId(int[] ids, int len) 53 { 54 //創建臨時id變量和計數器 55 int candidate = 0, nTimes = 0; 56 57 for (int i = 0; i < len; i++) 58 { 59 //如果計數器為0,那么將id賦值給candidate,並設nTimes=1,即改id出現的次數 60 if (nTimes == 0) 61 { 62 candidate = ids[i]; 63 nTimes++; 64 } 65 else 66 { 67 //如果本次出現的id跟candidate的相同,那么計數器加1,或者兩兩對消減1 68 nTimes = (candidate == ids[i]) ? nTimes + 1 : nTimes - 1; 69 } 70 } 71 return candidate; 72 } 73 74 //其實就是把這三個ID看成一個ID,然后跟另外一個ID對消(四個不同ID對消),對消之后他們的ID仍然會超過1/4 75 static int[] FindQuarterIds(int[] ids, int len) 76 { 77 int[] candidate = new int[3]; 78 int[] nTimes = new int[3]; 79 for (int i = 0; i < len; i++) 80 { 81 #region MyRegion 82 if (candidate[0] == ids[i]) 83 { 84 nTimes[0]++; 85 } 86 else if (candidate[1] == ids[i]) 87 { 88 nTimes[1]++; 89 } 90 else if (candidate[2] == ids[i]) 91 { 92 nTimes[2]++; 93 } 94 else if (nTimes[0] == 0) 95 { 96 candidate[0] = ids[i]; 97 nTimes[0] ++; 98 } 99 else if (nTimes[1] == 0) 100 { 101 candidate[1] = ids[i]; 102 nTimes[1]++; 103 } 104 else if (nTimes[2] == 0) 105 { 106 candidate[2] = ids[i]; 107 nTimes[2]++; 108 } 109 else 110 { 111 nTimes[0]--; 112 nTimes[1]--; 113 nTimes[2]--; 114 } 115 #endregion 116 } 117 return candidate; 118 } 119 } 120 }