原文链接:http://www.cnblogs.com/baiyanhuang/archive/2010/06/23/1763981.html
作者:@baiyanhuang
有 1 到 10000 共 10000 个数,如果我从中随机拿走一个数,你如何知道我拿走了哪个?
相信很多人看过这道题,并知道答案,这几天和同事聊天时听到了这个问题,因为有过自己的思考过程,不妨记录下来。 说是逻辑题,其实也算是一道算法题,同事先讲了下他被面试中的思维过程:
先把 10000 个数相乘,然后再将拿走一个数之后的 9999 个数相乘,两者相除即可。
这个算法是正确的,但是会有两个潜在的问题:
针对前面提出的问题,同事想到了使用加法,先求出 10000 个数的和,再减去 9999 个数的和。
这样数据不会溢出,而且加法的效率比乘法也要高很多,即使数据中包含 0,也没有任何问题。
然后就过关了,自己回去之后思考了一下,觉得还可以扩展,假设所有的数加起来之后仍然会溢出,那该如何处理,比如从 1 到 (2^64-1),于是想到了位操作,与、或,异或中,要数异或最为神奇,代入一看,果然合适: 先将所有的数异或起来,然后将拿走一个数之后的数异或起来,两者结果再异或,便是拿走的那个数。
我用 a,b,c,d 4 个数来做演示,因为异或符合结合律和交换律(你可以用 0,1 试一下),于是:
a^b^c^d = (a^b^c)^d d = (a^b^c^d)^(a^b^c)
此处用异或的好处在于
扩展一下题目,如果提供的不是整数,而是浮点数,会有问题吗? 当然没有,因为是在位级别上操作,无论是整数还是浮点数,在这个算法看来,都是一堆位,处理起来没有什么差别。
再扩展一下题目,如果提供的数本身就超出了内置类型的表示范围,如在 1 到 2^128,该如何处理? 这个问题是在写这篇文章的过程中想到的,暂时没有好的办法。
版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。
这个问题实际上是一个经典的算法问题,涉及到位操作的高效性和数据处理的技巧。异或(XOR)操作在这里起到了关键作用,因为它具有以下性质:
基于这些性质,无论数组中包含多少个元素,将所有元素进行异或操作的结果就是数组中所有元素的异或值。如果从这个数组中随机拿走一个数,然后对剩下的所有数再次进行异或操作,得到的结果将是被拿走的那个数与之前所有数异或结果的异或。由于第一次所有数异或的结果实际上就是被拿走的那个数(因为每个位上,如果有偶数个相同数字异或会得到0,奇数个则保留该位),所以第二次异或就相当于两次异或同一个数,即原数 ^ 原数 = 0
,因此最终结果就是被拿走的数。
对于题目中的扩展情况: - 浮点数处理:理论上,如果浮点数在内存中表示遵循IEEE 754标准,其二进制表示也是可以进行位操作的。但实际上,直接对浮点数的位进行异或操作可能不直接适用于此题目的逻辑,因为浮点数的存储结构包括符号位、指数部分和尾数部分,直接异或可能会破坏其数值意义。不过,如果仅考虑题目要求找出缺失的数,且假设所有浮点数都是正数且没有特别小的数导致精度丢失,那么在特定场景下,这种思路仍可尝试,但需谨慎处理。 - 超大范围数值:当数值超出内置类型表示范围时,如提到的1到2^128,直接使用常规的数据类型进行操作确实会遇到问题。这时,可以借鉴大数运算的思想,实现自定义的大数类来支持异或等位操作。具体实现上,可以将大数分解成多个字节或更大的单位进行逐位处理,确保不会溢出,并通过模拟位操作的逻辑完成计算。这种方法虽然复杂度增加,但在理论上是可行的,且能够处理任意大小的整数。
综上所述,异或操作因其独特性质,在解决此类问题时非常有效,但对于非整数或超大数值的处理,则需要根据具体情况采取额外的措施来保证算法的正确性和可行性。