今天刷了几道题,发现leetcode现在都有分类了,所以顺便把以前刷过的bit manipulation相关的题目都看了一遍,下面简单总结一下。
1. Single Number
题目大意:在给定的整数数组中,除了一个数字之外,其他数字都出现了两次,现让求出只出现一次的那个数字。
基本思想:遍历数组一次,将所有数字依次进行异或运算,结果即为所求的数字。因为其他数字都恰好出现了两次,异或得0。
2. Single Number II
题目大意:还是一个整数数组,但是这个数组中除了一个元素其他元素都出现了三次,现让求出只出现一次的那个数字。
基本思想:还是遍历一遍数组,但不止一次,而是32次(32位),每次看数组中所有元素在相应的位上的数字是0还是1,统计1的数目。若该位上1的数字整除3,则表示所求数字在该位为0,否则该位为1。(这种方法可以适用其他元素出现4,5,6...等次的情况)
int singleNumber(int A[], int n) {
int count = 0;
int result = 0;
for (int i = 0; i < 32; i++) {
count = 0;
for (int j = 0; j < n; j++) {
count += ((A[j] >> i) & 1);
}
result |= ((count % 3) << i);
}
return result;
}
3. Single Number III
题目大意:还是一个整数数组,但是这个数组中除了两个元素之外,其他元素都出现且仅出现两次,现在求只出现一次的两个数字。(为方便叙述,这里不妨设这两个数字为A和B)
基本思想:第一遍遍历数组,按照1中的方法可以求出C=A^B。我们可以利用C ^= (C & (C-1))将C变成了仅保留最低位的1的数字,这样C就可以用来作为区分A和B的标志。因为C是A和B按位异或的结果,C上的1即表示这位上A和B是不同的。因此第二遍遍历数组,我们将所有数字按照&C的结果分成两类,这样这两类分别异或后的结果即对应A和B。
vector<int> singleNumber(vector<int>& nums) {
vector<int> ret(2, 0);
int n = nums.size();
int aXORb = 0;
for(int i = 0; i < n; ++i)
{
aXORb ^= nums[i];
}
aXORb ^= (aXORb & (aXORb - 1));
for(int i = 0; i < n; ++i)
{
if(nums[i] & aXORb) ret[0] ^= nums[i];
else ret[1] ^= nums[i];
}
return ret;
}
4. Reverse Bits
题目大意:将32位的整数按位反转,输入和输出都是uint32_t。
基本思路:考验两种比较基础的位操作:取出一个整数某一位上的值,将一个整数的某一位上赋值(0或1)。有了这两个基本操作之后,bit的反正和字符串的反转就类似了。
uint32_t reverseBits(uint32_t n) {
uint32_t ret = 0;
for(int i = 0; i < 32; ++i)
{
if(n & 1) ret |= (1 << (31-i));
n >>= 1;
if(0 == n) break;
}
return ret;
}
5. Repeated DNA Sequences
参见 前几天的一篇博客 [leetcode] Repeated DNA Sequences
vector<string> findRepeatedDnaSequences(string s) {
vector<string> ret;
if(s.length() < 10) return ret;
unordered_map<char, int> mapNucleotide;
mapNucleotide['A'] = 0;
mapNucleotide['T'] = 1;
mapNucleotide['C'] = 2;
mapNucleotide['G'] = 3;
unordered_map<int, int> mapStrCnt;
int base = ~(3 << 18);
int bits = 0;
for(int i = 0; i < 10; ++i)
{
bits <<= 2;
bits += mapNucleotide[s[i]];
}
mapStrCnt.insert(pair<int, int>(bits, 1));
for(int i = 10; i < s.length(); ++i)
{
bits &= base;
bits <<= 2;
bits += mapNucleotide[s[i]];
if(mapStrCnt.find(bits) != mapStrCnt.end())
{
++mapStrCnt[bits];
if(2 == mapStrCnt[bits]) ret.push_back(s.substr(i-9, 10));
} else {
mapStrCnt.insert(pair<int, int>(bits, 1));
}
}
return ret;
}
6. Number of 1 Bits
题目大意:求一个无符号整型的二进制表示中有多少个1。
主要思路:最暴力的办法就是每一位遍历一遍看是否为1,求出总数。然而位运算能够为我们提供一个O(k)的算法,这里k即为1的个数。这个位运算就是n & (n-1),因为n和n-1的二进制表示中肯定高位部分是相同的,低位部分是相反的,两者进行与运算后恰好可以把n的二进制表示中最低位的1消除。
int hammingWeight(uint32_t n) {
int ret = 0;
while(n > 0)
{
++ret;
n &= (n-1);
}
return ret;
}
7. Power of Two
题目大意:判断一个整数是否为2的幂。
基本思路:搞清楚2的幂的特征即可,就是二进制表示中只有一位是1,其他位全是0。当n的二进制表示只有一个1的时候,n & (n-1)就等于0。不过这题需要注意的是非负数都要排除,2^0 = 1,所以2的幂最小是1,我竟然有一瞬间认为0和-4都是2的幂,没文化好可怕!!
bool isPowerOfTwo(int n) {
if(0 >= n) return false;
else return ((n & (n-1)) == 0);
}
8. Missing Number
题目大意:A为含有n个元素的整数数组,其中这n个元素各不相同,且取值范围为[0, n],因此[0, n]中肯定存在一个数字没有出现在A中,求出这个数字。
基本思路:A中所有元素进行异或运算得到XOR1,1到n进行异或运算得到XOR2(这里之所以用到1..n的所有数字异或是考虑到奇偶个数字的影响),所求的数字即为XOR1与XOR2的异或。当然还有一种更直观的方法是求和公式n(n+1)/2-sum(A)。
int missingNumber(vector<int>& nums) {
int n = nums.size();
if(n == 0) return -1;
int ret = 0;
for(int i = 0; i < n; ++i)
{
ret ^= nums[i] ^ (i+1);
}
return ret;
}
9. Bitwise AND of Numbers Range
题目大意:给定两个非负整数m, n (m <= n),求出m到n所有数字(包含m和n)按位与的结果。
基本思路:注意按位与运算的特性,0与任何元素都是0,所以某一位上只有这些数字中有出现0,则最终结果上该位就是0。那么我们再来观察m和n,当m==n时,结果为m本身;当n更大时,m和n的二进制表示中肯定是高位部分相同,低位部分不同。值得一提的是,m到n之间连续的数字的二进制表示是通过+1实现1逐渐向高位翻转的(这并不重要),所以m和n不同的低位部分中,这些数字里可定存在数字在这些为上是0,所以所求结果在这些位(m和n不同的低位部分)都是0。那么m和n相同的高位部分呢?所有的数字的这些位都是一样的,所以按位与的结果还是它本身。
int rangeBitwiseAnd(int m, int n) {
int i = 0;
for(; m!=n; m>>=1,n>>=1) ++i;
return m<<i;
}
另外两道题目似乎与位运算无关,或者是我比较愚钝,没有找到位运算的方法去解题,所以就不列在这个总结里了。