在《编程之美》的1.5节,题目大意是这样的:有很多服务器,假设一个服务器仅存储一个标号为ID的记录,并且机器总量在10亿以下,所有ID均为小于10亿的整数,假设每份数据保存两个备份,这样就有两个机器存储了同样的数据。问题是:
1.假设在某个时间得到一个数据文件ID的列表,是否能快速地找出表中仅出现一次的ID?即快速找出出现故障的机器存储的数据ID。(这里先假设只有一台机器发生故障)
2.如果有两台机器出现故障呢?(假设存储同一份数据的两台机器不会同时出现故障,即列表中缺少的是两个不等的ID)
现在先来讨论问题1。这个问题实质就是快速找出大集合中丢失的数字。
【解法一】对于列表中的每一个ID,遍历列表查找是否存在相同的ID。找到那个没有与其相同的ID的ID即为所求。这种方法时间复杂度为O(N^2),空间复杂度为O(N)。也可以事先对ID列表排序,然后顺次遍历列表,查找同一ID是否在相邻的位置出现两次。这样可以使时间复杂度降为O(N*logN)。但是这些方法对于ID数目庞大的情况不适用,因为ID数目大的话,只能采用外排序,而且需要多次遍历列表。
【解法二】遍历列表,记录每一个id出现的次数,出现次数为1的ID即为所求的ID。这种方法比较简单,出现次数小于2的ID即为所求。时间复杂度为O(N),这个方法需要一个记录ID出现次数的数组,所以,空间复杂度为O(N)。时间复杂度已经很优,空间复杂度在N很大时不理想。
【解法三】利用hash表。遍历列表,对于每一个ID,先检查hash表中是否有与之相同的ID,若有,则从Hash表中删除该ID;否则,将该ID加入到hash表中。这样,遍历完列表后,hash表中剩下的那一个元素即为所求ID。这种方法时间复杂度为O(N),空间复杂度在最好的情况下为O(1),在最坏的情况下为O(N)。
【解法四】利用异或运算。事实上,将这个列表中的所有ID异或后的值即为所求ID。这种方法时间复杂度为O(N),空间复杂度为O(1)。在时间和空间上,基本已经达到最优。
【解法五】利用“不变量”。所有ID的和为一个不变量,对剩下ID求和。所有ID的和与剩下ID的和之差即为所求ID。由于所有ID之和可以事先算好,所以,该方法也可以在O(N)时间,O(1)空间内解决。
总体来说,解法四和解法五均已经很优化。他们均能在只遍历一次列表,只需一个变量的条件下解决。
对于问题2,我们可以在上面的解法上改进。解法三是一中比较通用的解法。对于解法四和解法五则需要构造方程来解决。
【扩展问题】如果同时有A个备份,同时有B台机器发生故障,怎么解决?个人觉得,在B小于4的情况下,还可以考虑采用解法五构造方程解决。但是,B超过4的情况下,方程势必很复杂,建议采用解法三,当然,解法三需要改进。需要对存入Hash表的ID计数,当计数值达到A时再将这个ID从hash表中删除。
【相关问题】直接采用解法五。事先算好所有牌的和(1+…+13) x 4 = 364,然后分别减去留下的牌点数,就得到抽出的是哪一张。
by lgjfly,2010.11.20