呵呵,博客园的hello world文章.今天在学习编程之美书上的题,遇到了这个题.具体的题目我就不来讲了,博客园里有很多这道题的说明.我想侧重讲一下对书中提出的解法的理解.
这道题要解决的问题,可以概括为:在无序且存在重复的序列中,找出重复最多的那个元素.前提:重复最多的元素超过总数的一半.
按照传统的解法,当然是先排序,然后再扫描一遍,记录每个元素的出现次数.这样的话,正如书中所说,复杂度为O(N*logN + N).针对这道题,考虑到已知重复最多的元素的数量是超过总数的一半的,那么我们可以不需要排序,而是每次删除两个不同的元素,那么在剩下的序列中,重复最多的那个元素还是依然超过总数的一半.这可以证明之.
求证: N个含有重复的整数中,其中重复次数最多的整数ID,其数量为K,且 k>N/2.证明当删除两个不同的整数后,重复最多的元素依旧是该元素,且数量依然超总数一半.
证明: 情况1,删除的两个整数都ID,那么显然ID在新序列中所占比值更大了.
情况2,删除的两个整数中,其中有一个是ID,那么ID的新比值为(K-1)/(N-2). 那么K/N - (K-1)/(N-2) = (N-2K)/N(N-2), 由于K>N/2,可知K/N < (K-1)/(N-2), 即ID的比值更大了.
于是得证.
根据这个结论,我们只需要设计一段程序,遍历序列的过程中不停地将不同的元素删除即可.但是书中的代码似在我的设计水平之上,一开始不能理解是如何运用上面的结论的.他的代码是这样的:
View Code
1 int Find(int *ID, int n)
2 {
3 int candidate;
4 int nTimes,i;
5 for(i=nTimes=0; i<n; ++i)
6 {
7 if(nTimes==0)
8 {
9 candidate = ID[i];
10 nTimes=1;
11 }
12 else
13 {
14 if(candidate == ID[i])
15 {
16 nTimes++;
17 }
18 else
19 {
20 --nTimes;
21 }
22 }
23 }
24 return candidate;
25 }
花了一段时间,我终于理清了,下面给出我的理解:
1. Find函数从0->N遍历整个ID序列;candidate最终将指向重复最多的ID;nTimes记录所指candidate的重复次数,初始为0.
2. 开始遍历,首先将candidate初始为ID[0],nTimes设为1;
3. 遍历下一个,如果当前nTimes为0(表示目前已经没有与candidate相同的ID可用了),则将candidate指向当前ID(相当于删除了之前的candidate),nTimes=1;否则4;
4. 如果candidate所指与当前ID相同,那么nTimes加1(相当于找到"替死鬼");否则减1(按理来说是要candidate删除并前移的,但是这里相当于杀了"替死鬼",等到nTimes=0时,直接跳到此时所指的ID);
5. 重复3, 直到最后.返回candidate.
这段代码是具有特殊性的,即有前提条件.如果最多重复的ID没有超过一半,则行不通了.但对于书中的扩展,条件是已知有三个重复很多的ID,且都超过了总帖数的1/4.这可以用类似的方法,即每次减去4个不相同的元素.具体的也就是用nTimes[3]分别统计每个待找ID的"替死鬼"个数.代码结构和上述类似.
Ok,从今天起坚持每天至少写一篇关于算法的解题思路,不管大小,重在积累.相比园中老鸟,我貌似为时已晚.但又以哈佛那句训言自勉:觉得为时已晚的时候,恰恰是最早的时候.对于这份一辈子的事业,为时不晚.

浙公网安备 33010602011771号