前言
位运算。机器码。这里有个坑。
题目
Acceptance : 38% Difficulty : Medium
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?
Tags Bit Manipulation
题解
之前说过Single Number I 和 Single Number III。这个II 直到昨晚还是没能想出一个利用 Bit Manipulation 的方法。想到了两种方法,但不符合 Note 的提示。
1. 利用hash表,对每个数字建表,value = 出现次数。然后从头遍历hash表找到value = 1的那个key。
2. 排序,然后遍历。if (nums[i] = nums[i+1]) i = i + 3;else return nums[i]。这种算法复杂度就是排序的复杂度+n/3。
后来百度了一下找到了利用位运算解题的方法。
一句话概括就是:按位找到孤异数字的二进制数。
这种方法可以推广到其他数字都出现了n次的情况。
以其他数字是3,孤异数字是2为例。
右数第一位是1的数字共出现了n次,那么可以得出孤异数字的右数第一位数字是0。
右数第二位是1的数字共出现了n+1次,那么可以得出孤异数字的右数第二位数字是1。
右数第三位是1的数字共出现了0次,那么可以得出孤异数字的右数第一位数字是0。
。。。。
。。。。
于是,这种方法的时间复杂度就是 32n(粗略算,认真算的话,是遍历32次,每次需要n次与操作,n/2次自加操作,和1次或操作)。其实跟使用快排排序后再遍历的方法。只有当32 < log2n,也就是说,n > 232(粗略算)时,这种位运算的方法才体现出优越性。
OK,上代码:
AC Code
class Solution
{
public:
int singleNumber(vector<int>& nums)
{
if (nums.size() == 1)
return nums[0];
int mask = 1, ans = 0;
while(mask)
{
int cnt = 0;
for (int j = 0; j < nums.size(); j++)
{
// if(mask & nums[j] > 0) // There is a cheating pit //#code1
// if ((mask & nums[j]) > 0) // It is a cheating pit, too. //#code2
if (mask & nums[j]) //#code3
cnt++;
}
if (cnt % 3 > 0)
ans |= mask;
mask <<= 1;
}
return ans;
}
}
嗯。当我写完#code1时,用了个Custom Testcase [1, 1, 1, 2]。Run,出错了。于是在VS里单步调试。发现它一直在跳入cnt++语句。即使nums[j] = 2。
所以,如果这里不加括号使之变成#code2形式的话,那么这句if的判断过程是,先判断 nums[j] > 0,然后用mask & 刚刚的结果true。相当于true&true。所以计算出来的结果是错的。
必须强调的是,当判断句里用了位运算的时候,为确保答案正确,尽量把该有的括号加上去。
然而,当我用#code2进行Submit时,WA。又是一个值得注意的事情当nums[j]为负数的时候,计算结果就混乱了。
bool b = 2 & -2;
bool c = 1 & -2;
bool d = (1 << 31) & -2;
int ib = 2 & -2;
int ic = 1 & -2;
int id = (1 << 31) & -2;
把以上代码compile,debug,发现
b = true;
c = false;
d = true;
ib = 2;
ic = 0;
id = -2147483648。(0xffff ffff 8000 0000)
而 int ie = 1 << 32 。= 0.
也就是d和id的不同,(id < 0),而(d)为true。所以会导致WA。
那么,具体的更深入的原因是什么呢?
再看 bool e = (1 << 23) & -2, e = true;
int ie = (1 << 23) & -2, ie = 8388608;
-2的32位机器码表示为0xffff fffe。
那么原因就是,当使用#code2时,从第1位(右数第一位记为第0位)到第30位都将被 |=,而第31位(左数第一位)则因为 id < 0而未被 |=。
于是结果就成了 0x7fff fffe。 十进制的2147483646。
而当使用#code3时,从第1位到第31位全会因true而被 |=。将得到正确答案 0xffff fffe。即十进制的 -2。