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 }