问题聚焦:
每个ID记录有两个备份。
在某个时间,得到一个数据文件ID的列表,快速找出这个表中仅出现一次的ID。
问题转化:
有很多的ID,其中只有一个ID出现的次数小于2,其他正常ID出现的次数都等于2,问如何找到这个次数为1的ID.
解法一:暴力解决
方法:遍历列表,利用一个map记下每次出现的ID和出现次数+1,遍历完毕之后,出现次数小于2的ID就是我们想要的结果。
时间复杂度:O(N)
空间复杂度:O(N)
缺点:当记录N多达几G甚至几十G时,空间复杂度将会成为瓶颈。
解法二:两次即删
方法:遍历列表,利用变长数组记录每个ID,每次遇到一个ID,就向变长数组中增加一个元素,如果这个ID出现的次数为2,那么就从变长数组中删除这个ID, 最后变长数组中剩下的ID就是我们寻找的ID。
时间复杂度:O(N)
空间复杂度:最好O(1),最坏O(N)
解法三:异或运算
思路:摒弃遍历列表技术方式
目标:把空间复杂度降低到常数甚至为1的级别,即使用一个变量来记录遍历列表的结果
构造函数:
x(i) = f(List[0], List[1], List[2], … , List[i]) 即:这个变量是已经遍历过的列表元素的函数
该函数需要满足的条件:x(N) = ID_LOST
运算:
异或
性质:X 异或 X = 0, X 异或 0 = X, 异或操作满足交换率和结合律
方法:所以的ID的异或值就等于这个仅出现一次的ID。
时间复杂度:O(N)
空间复杂度:O(1)
解法四:寻找不变量 方法:所有ID的和是不变的,所以用所有ID的和减去现有ID的和即得丢失的ID。 时间复杂度:O(N) 空间复杂度:O(1) 缺点:不适用于丢失多个ID的情况
问题进阶一:当有两个ID的机器一起出现故障,确定出现故障的机器
如果缺少的两个ID不相同
解法:异或运算
- 对所有ID进行异或运算,结果为a(不等于0)
- 确定a的某一个为1的二进制位置b
- 将所有ID分为两组:二进制位置b为1的为一组A,二进制位置为0的为一组B
- 对AB两组分别进行异或运算,得到两个不为0的数字,即为丢失的两个ID
如果不能确定缺少的两个ID是否相同
解法一: 因为已知丢失两个数是相同的,所以通过上面的解法四可以得到 x + y = a, x * 2 = a, x = a/2 当然,这种方法是建立在已知两个数是相同的前提下的。
解法二:(通用解法,对丢失N个数的情况同样适用) 如果不知道两个数是否相同,可以通过建立方程组来解决 解法一已经给出了一个方程组: x+y=a 那么再计算丢失前后的所有ID的平方和,进行相减,可以又得到一个方程: x^2 + y^2 = b 联立方程组即可求解。
问题进阶二:当有多个ID的机器一起出现故障,确定出现故障的机器
解法一: 参考问题进阶一解法二。 缺点:当N过大时,N个方程组不易求解。
解法二: 参考解法二,遍历+计数+两次即删。
参考: 《编程之美3rd》
http://blog.csdn.net/insistgogo/article/details/7687936
编程之美_1.5_快速找出机器故障