假设有一组奇数个数字,只有一个数字是单独的,其他数字都是成对的。那么怎么才能找到到这个数字。要求时间复杂度为O(N)。
桶排序/map
一般来说,去重都是通过桶排序来完成的。或者是使用map存放键值对的方式来完成。在算法的层次上没什么难度,略过不表。
异或运算
现在,要求空间复杂度为O(1)。那还有什么方法呢?
解法可以说是会者不难,难者不会。实际上只需要将所有数字进行异或,最后得到的就是那个单独的数字。这个解法的原理实际上就是异或的交换律、结合律以及两个相同的数字异或结果为0。
增加一个数字
如果说有两个单独(而且不同)的数字,可以运用技巧,仍然运用异或来做。
首先要故技重施,最后可以得到两个数的异或值,这个异或值必不为零(否则可以推出这两个数字相同),那么取有1的一位,将整个集合分解为此位为1或此位为0两种情况,就能够用同样的方法得到这两个数字。
再增加一个数字
如果是三个数字?那么利用异或的方法就不一定好用了。假设三个数满足x^y=z。那么这个异或的结果就是0。好了,在这一位上,根本没办法把三个数分开,因为必然是两个相同的数字+一个0。分类以后,只能知道是2+1还是3+01。虽然不可能所有的0位都是3+0,但是有可能是倒数第两位才能分出来呢~
总之,经过o(logn)2次运算以后(一次运算的复杂度是O(n)哦),总算是能够找到那个2+1了。于是回到了我们熟悉的内容。
那么可能有人会问:如果x^y=~z,全1的结果该怎么办呢?如果有1位1,那么就是两个相同的数字+一个1了。很遗憾,并不能通过1和0的区别来优化这个算法的时间复杂度。
这个时候,算法复杂度甚至不如简简单单去做一次排序了。
再增加很多个数字3
首先我们有0^x=x以及1^x=!x。显然:如果有奇数个1,就会在这一位上得到1,否则就会得到0。
然后,由于这些数字各不相同,必然可以通过逐步划分来实现这个问题的分治。大致的思路就是通过各种分治将整组数字划分成许多小块。唯一的问题,就是当你将异或结果划分成两部分以后,判断其中一部分是否只包含1个数据不再像只有3个数字时那么显然了。唯一的方法就是对这个部分尝试进行完整的一次分离,如果所有的分离结果都返回了一个0与那个异或结果,那么它就是一个数字,而不是几个数字异或得到的结果。
所以,这个算法的效率甚至比3个数字的时候更为低效。不过至少这个算法可以停机……毕竟本来也没对这种情况抱什么期待。
一点儿吐槽
我被问到这个题目的时候,直接问的就是怎么找两个数……这个就太为难人啦……
我大概可以推测出这个出题思路——有个家伙有一天想:如果我用异或运算来去重,那么什么情景下可以做到呢?那么重复成对肯定是必要条件。如果是找一个单独的数字,可以说还是可以想得到异或的。找两个数字的话实际上如果没有顺着一个数字的思路去想就会很困难,但是顺着异或一个数字的思路就会大大简化。
作为实际的问题吧,这样的情况是少之又少,不太具有普遍性。作为考核吧,就和做数学题的时候第一小题不做就去做第二个小题一样……
如果是3+0,得到的结果必然是0和第一次异或的时候得到的数字。所以如果得到了不一样的结果,显然就是1+2的分离。 ↩︎
这里理所当然地假设了n个不同数字的位数是log n级别的。但是……n至少需要用log n来表示和实际上使用的位数显然半点关系都没有!所以这里用了少见的小o表示下限,因为上限就是无极限(我完全可以存n个googolplex4级的超大数,你来打我啊)。从这个角度来说,这个时候还想用异或解决问题已经很不现实了。 ↩︎
一般来说,这样做真的不值得。这里只是从理论上的可能来讨论这个解决问题的方法,作为一种节省空间的极端举措。 ↩︎
我也没想到注释里面可以套注释。回到正题:什么是googolplex数。 ↩︎