首先想到的是一个最直接的方法,咱们能够对全部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; }