前几天ZJ问了一个问题,大意是一个数组,其中除了一个数字只出现一次以外其他的数字都出现了三次。问怎么样高效的求出只出现一次的那个数字。
很容易的想到维护一个32*4Byte大小的数组(输入数据为32bit整形),然后通过对输入数字的每一个二进制位进行计数求解。该算法利用到的性质是如下:
X= 2^30 * X30 + 2^29 * X29 + ... + 2^0 * X0 ;
Y = 2^30 * Y30 + 2^29 * Y29 + ... + 2^0 * Y0;
X + Y = 2^30 * (X30+Y30) + 2^29 * (X29+Y29) + ... + 2^0 * (X0+Y0)
于是我们可以发现一个事实。
X + X + X = 2^30 * (X30+X30+X30) + 2^29 * (X29 + X29) + ... + 2^0 * (X0 + X0 + X0)
如果我们对上述式子中的系数进行取模运算的话。会发现有趣的现象。 定义运算如下:
mod( X , Y , N ) = 2^30 * ( ( X30 + Y30) % N ) + 2^29 * ( ( X29 + Y29 ) % N ) + ... + 2^0 * ( ( X0 + Y0 ) % N)
DEF: Xi= 2^30 * Xi,30 + 2^29 * Xi,29 + ... + 2^0 * Xi,0
mod( X1 , X2 , X3 , ... , Xn , N ) = 2^30 * ( ( X1,30 + X2,30 + X3,30 + .. + Xn,30) % N ) + 2^29 * ( ( X1,29 + X2,29 + ... + Xn,39 ) % N ) + ... + 2^0 * ( ( X1,0 + X2,0 + ... + Xn,0 ) % N)
容易验证该运算是满足交换和结合性质的。
那么我们再来看文首我提的那个问题. X1 + X2 + ... + Xn = (Xk1 + Xk1 + Xk1) + ( Xk2 + Xk2 + Xk2) + .... + ( Xkj + Xkj + Xkj ) + Xk(j+1) , 其中j = n / 3;
对这N个数进行新定义的mod运算 , 有mod( X1 , X2 , X3 , ... , Xn , N ) = mod ( Xk1 , Xk1 , Xk1 , Xk2 , Xk2, Xk2, ... Xkj, Xkj , Xkj , Xk(j+1) , N ) = ?
问题应该说迎刃而解了。但是我们现在仅仅从理论上面认证了该算法的可行性,是否需要改进呢?
让我们来看该问题的复杂度, 从时间上面分析 , 我们对每一个数需要取出它相应二进制权值位系数 。 然后累加, 取模。 容易看出复杂度是线性的。但是仔细想想 , 取出对应进制位系数这步操作, 对二进制而言需要31次运算, 这个复杂度系数值能否改进呢? 能否用十进制取代? 无论是否可以 , 思考的过程是重要的 。Hoho.
接下来分析空间复杂度, 我在第一段说需要维护一个数组, 相信大家看到这里已经知道该数组是用来存放相应权值位的累积和的了。 因为只需要常数空间 , 与N 无关 , 所以空间复杂度是0(1)。 但是值得一提的是这个还有改进的地方。 如果我们在进行系数累加的过程中 , 不断进行%3操作,那么会发现每一个数组元素的值都不会超过3. 于是用一个4Byte的空间来存放一个不大于3的数是不是浪费呢。 虽说改进不一定明显,但是还是那句话,思考是重要的。