快速找出故障机器
我们假设一个机器仅存储一个标号为ID的记录(假设ID是小于10亿的整数),假设每份数据保存两个备份,这样就有两个机器存储了同样的数据。
- 在某个时间,如果得到一个数据文件ID的列表,是否能够快速地找出这个表中仅出现一次的ID?
- 如果已经知道只有一台机器死机(也就是说只有一个备份丢失)呢?如果有两台机器死机呢(假设同一个数据的两个备份不会同时消失)?
解法一:
这个问题可以转化为,有很多ID,其中只有一个ID出现的次数小于2,其余正常ID出现的次数都等于2,问如何找到这个次数为1的ID。
为了达到这个目的,最简单的方法就是直接遍历列表,利用一个数组记下每个ID出现的次数,遍历完毕之后,出现次数小于2的ID就是我们想要的结果。
假设有N个ID,且ID的取值在0~(N-1)之间,这个解法占用的时间复杂度为O(N),空间复杂度为O(N)。
空间复杂度并不理想,如果ID的数量多达几十GB,这样的空间复杂度在实际运算中会带来效率问题,考虑进一步减少空间复杂度。
解法二:
哪些数据是不必存储的?大部分的机器ID出现次数都等于2,这些机器肯定不是故障的机器,可以不予考虑,可以把解法1数组中等于2的元素清空,然后用来存储下一个机器ID的出现次数,这样就可以减少需要的空间。
具体方法如下:遍历列表,利用哈希表记下每个ID出现的次数,每次遇到一个ID,就向哈希表中增加一个元素,如果这个ID出现的次数为2,那么就从哈希表中删除这个ID,最后剩下的ID就是我们想要的结果。这个算法空间复杂度在最好情况下,可以达到O(1),在最坏情况下仍然为O(N)
解法三:
如何进一步降低空间复杂度?
如果想继续降低空间复杂度,就要摒弃遍历列表计数这种方法了。
是否可以降到常数级别(甚至O(1))呢?
如果这样,我们可以把这个变量写成:x(i) = f(list[0],list[1],…,list[i]),也就是说这个变量是已经遍历过的列表元素的函数。
这个函数需要满足的条件是:x(N) = ID_Lost。
也就说遍历完整个列表后,这个变量的值应该等于丢失备份的ID。
我们需要找到整个f
可以考虑使用异或关系来帮忙找到结果。
因为正确的机器都会有两个ID,不正确的机器只有一个ID。所以所有ID的异或值就等于这个仅出现一次的ID(因为异或运算满足交换律和结合律,其他出现两次的ID异或完都为0)。这样我们只使用一次遍历运算就得到了只有一台故障机器情况下故障机器的ID。
因此,就可以使用 x(i) = list[0]⊕list[1]⊕…⊕list[i]
这种情况下,时间复杂度为O(N), 空间复杂度为O(1)。
对于第二问:由于有两个ID仅出现一次,设它们为A和B,那么所有ID的异或值为A⊕B。无法确定A和B的值
如果A=B,则A⊕B为0,也就是说丢失的是同一份数据的两个拷贝,我们可以通过求和的方法得到A和B(A=B=(所有ID之和-所有正常工作机器ID之和)/2)
如果A⊕B不等于0,那么这个异或值的二进制中某一位为1,显然,A和B中有且仅有一个数的相同位上也为1。
我们就把所有ID分成两类,一类在这位上为1,另一类这位上为0,那么对于这两类ID,每一类分别含有A和B中的一个。那么我们使用两个变量,在遍历列表时,分别计算这两类ID的异或和,即可得到A和B的值。
解法四:
如果需要考虑死机的两台机器ID相同的情况,还有没有办法解决?
这个问题可以抽象为:在一个事先预定的整数(ID)集合当中,怎么样找出其中丢失的一个数(ID)或两个数(ID)?
对于一个数,预先存好所有ID的和,然后减去后来的,就得到少了的那一个
对于两个数,预先存好所有ID的和以及积(为降低数据大小,还可以改为存平方和),然后两个方程求两个未知数
扩展问题
如果是N个备份?
N小的时候列几个方程?
N多的时候还是用遍历数组?