题目源自于leetcode。好题。用位运算来模拟多进制模加法。
题目:Given an array of integers, every element appears three times except for one. Find that single one.
Note:Your algorithm should have a linear runtime complexity. Could you implement it without using extra memory?
题目提示要用O(N)时间, O(1)空间。
思路:这道题要比http://blog.csdn.net/ojshilu/article/details/9336235 难一些。同一系列的还有http://blog.csdn.net/ojshilu/article/details/9336577,都应该往位运算上去想。
int类型有32位,假如二进制位不进位的话,对数组中除了single数以外的所有数做累加,32位每一位上的值都是3。假如我对每一位是做模3加法的话,把数组所有数加起来,32位每一位的值应该有1有0,因为三个相同数加起来都会模3变为0,所以剩下的1都是属于single数的,所以这个累加和就等于single数。
这个方法时间是O(32*N),但要分别计算每一位的累加,分别作32次累加。空间是O(1)。
class Solution {
public:
int singleNumber(int A[], int n) {
int result = 0;
int sum;
int i, j;
for(i=0;i<32;i++)
{
sum = 0;
for(j=0;j>i)&1); //每轮只对固定某一位做累加
if(sum%3 == 1)
result = result|(1<
方法一之所以要单独计算每一位的累加,是因为二进制位无法实现能累加到3并且还不进位。想要不进位(不进位的话才能并行着操作),我们就使用与或非位运算;想要能累加到3,我们需要自定义一些规则来实现它。
我想用与/或/非/异或来模拟实现位的模3加法。这样的加法有三个状态0、1、2,但是二进制位只有0、1两个状态,所以我需要借助一个数来作辅助。int型变量ones它的每一位的意义是,如果该位为1,则表示该位的当前累加和为1。int型变量twos它的每一位的意义是,如果该位为1,则表示该位的当前累加和为2。那么如果某一位在ones和twos都为0,说明该位的当前累加和为0。(下面制定的加法规则会让ones和twos中的某位不可能都是1)
那么加法的过程有这几种情况,对于某一位,ones和twos中的对应位如何变化:
1、加数X为0:ones不变,twos不变。
2、加数X为1:若之前ones为1,则ones变0,twos变1。
若之前twos为1,则ones还是0,twos变0。
若之前都为0,则ones变1,twos还是0。
具体的情况如下表格:
one | two | X | newone | newtwo | 加法 | ||
0 | 0 | 0 | 0 | 0 | 0+0=0 | ||
0 | 1 | 0 | 0 | 1 | 2+0=2 | ||
1 | 0 | 0 | 1 | 0 | 1+0=1 | ||
0 | 0 | 1 | 1 | 0 | 0+1=1 | ||
0 | 1 | 1 | 0 | 0 | 2+1=0 | ||
1 | 0 | 1 | 0 | 1 | 1+1=2 |
观察上面的表格,这个过程很类似数字电子课程里面的组合逻辑电路设计。
根据规律,构造出一种运算(肯定不只有这1种):
ones = (~twos) & (ones ^ X); (一定注意:执行完这句之后,ones值就变成新的了!)
twos = (~ones) & (twos ^ X);
对题目数组中所有数累加之后,必定是twos全部为0,ones有1有0,此时的ones就是我们要找的single数。
方法二的时间复杂度更好,只累加一次即可。这种用位运算模拟加法的方法可以达到多个位同时相加的并行的效果。当然它的使用限制是,累加最终结果的情况是要提前知道的,这里就是知道累加最终一定是ones有1有0,twos全0,累加结果就体现在这里。
代码:
class Solution {
public:
int singleNumber(int A[], int n) {
int ones = 0;
int twos = 0;
int i;
for(i=0;i
方法二的思想源于http://www.cnblogs.com/daijinqiao/p/3352893.html