位操作来轻松高效的解决问题
2018-03-12 10:45
176 查看
维基
位操作是通过算术操作位或其他短于数据的数据段的操作。需要位操作的计算机编程任务包括低级设备控制,错误检测和纠正算法,数据压缩,加密算法和优化。对于大多数其他任务,现代编程语言允许程序员直接使用抽象而不是代表抽象的位。进行位操作的源代码使用按位操作:AND,OR,XOR,NOT和位移。在一些情况下,位操作可以避免或减少循环遍历数据结构的需要,并且可以提供多倍的加速,因为位操作是并行处理的,但代码可能变得更难以编写和维护。
细节
基本
位操作的核心是位操作符&(和),| (或),〜(not)和^(exclusive-or,xor)和移位运算符a << b和a >> b。没有布尔运算符对应于按位异或,但有一个简单的解释。如果两个输入中的任何一个或另一个输入是1,则异或操作取两个输入并返回1,但如果两者都不相同则返回1。也就是说,如果两个输入都是1或两个输入都是0,则返回0.按位异或 - 对于^符号的操作符^,对每对位执行异或运算。独占 - 或通常缩写XOR。
设置联合A | 乙
设置路口A和B.
设置减法A&〜B
设置否定ALL_BITS ^ A或〜A
设置位A | = 1 <<位
清除位A&=〜(1 <<位)
测试位(A&1 << bit)!= 0
提取最后一位A和-A或A&〜(A-1)或x ^(x&(x-1))
删除最后一位A&(A-1)
获取所有1位〜0
例子
计算给定数字的二进制表示中的个数
int count_one(int n) { while(n) { n = n&(n-1); count++; } return count; }
是四权力(实际上是地图检查,迭代和递归方法可以做同样的事情)
bool isPowerOfFour(int n) { return !(n&(n-1)) && (n&0x55555555); //check the 1-bit location; }
^技巧
使用^删除甚至完全相同的号码,保存奇,或保存在不同的位并移除相同。
两个整数之和
使用^和&添加两个整数
int getSum(int a, int b) { return b==0? a:getSum(a^b, (a&b)<<1); //be careful about the terminating condition; }
缺少数字
给定一个包含从0,1,2,…,n中取出的n个不同数字的数组,找到数组中缺少的数字。例如,给定nums = [0,1,3]返回2.(当然,你可以通过数学来做到这一点。)
int missingNumber(vector<int>& nums) { int ret = 0; for(int i = 0; i < nums.size(); ++i) { ret ^= i; ret ^= nums[i]; } return ret^=nums.size(); }
| 技巧
保持尽可能多的1位
找到2的最大幂(二进制形式的最高有效位),它小于或等于给定数N.
long largest_power(long N) { //changing all right side bits to 1. N = N | (N>>1); N = N | (N>>2); N = N | (N>>4); N = N | (N>>8); N = N | (N>>16); return (N+1)>>1; }
反转位
反转给定的32位无符号整数的位。
解
uint32_t reverseBits(uint32_t n) { unsigned int mask = 1<<31, res = 0; for(int i = 0; i < 32; ++i) { if(n & 1) res |= mask; mask >>= 1; n >>= 1; } return res; } uint32_t reverseBits(uint32_t n) { uint32_t mask = 1, ret = 0; for(int i = 0; i < 32; ++i){ ret <<= 1; if(mask & n) ret |= 1; mask <<= 1; } return ret; }
& 技巧
只需选择某些位
颠倒整数中的位
x = ((x & 0xaaaaaaaa) >> 1) | ((x & 0x55555555) << 1); x = ((x & 0xcccccccc) >> 2) | ((x & 0x33333333) << 2); x = ((x & 0xf0f0f0f0) >> 4) | ((x & 0x0f0f0f0f) << 4); x = ((x & 0xff00ff00) >> 8) | ((x & 0x00ff00ff) << 8); x = ((x & 0xffff0000) >> 16) | ((x & 0x0000ffff) << 16);
按位与数字范围
给定范围[m,n],其中0 <= m <= n <= 2147483647,返回此范围内所有数字的按位与(包含)。例如,给定范围[5,7],您应该返回4。
解
int rangeBitwiseAnd(int m, int n) { int a = 0; while(m != n) { 4000 m >>= 1; n >>= 1; a++; } return m<<a; }
1位数
编写一个采用无符号整数的函数,并返回其具有的’1’位数(也称为汉明权重)。
解
int hammingWeight(uint32_t n) { int count = 0; while(n) { n = n&(n-1); count++; } return count; } int hammingWeight(uint32_t n) { ulong mask = 1; int count = 0; for(int i = 0; i < 32; ++i){ //31 will not do, delicate; if(mask & n) count++; mask <<= 1; } return count; }
应用
重复的DNA序列
所有DNA由一系列缩写为A,C,G和T的核苷酸组成,例如:“ACGAATTCCG”。在研究DNA时,识别DNA中的重复序列有时很有用。编写一个函数来查找DNA分子中出现多次的10个字母长的序列(子串)。
例如,
给定s =“AAAAACCCCCAAAAACCCCCCAAAAAGGGTTT”,
返回:[“AAAAACCCCC”,“CCCCCAAAAA”]。
解
class Solution { public: vector<string> findRepeatedDnaSequences(string s) { int sLen = s.length(); vector<string> v; if(sLen < 11) return v; char keyMap[1<<21]{0}; int hashKey = 0; for(int i = 0; i < 9; ++i) hashKey = (hashKey<<2) | (s[i]-'A'+1)%5; for(int i = 9; i < sLen; ++i) { if(keyMap[hashKey = ((hashKey<<2)|(s[i]-'A'+1)%5)&0xfffff]++ == 1) v.push_back(s.substr(i-9, 10)); } return v; } };
但是当重复序列出现太多次时,上述解决方案可能无效,在这种情况下,我们应该用这里unordered_map
int majorityElement(vector<int>& nums) { int len = sizeof(int)*8, size = nums.size(); int count = 0, mask = 1, ret = 0; for(int i = 0; i < len; ++i) { count = 0; for(int j = 0; j < size; ++j) if(mask & nums[j]) count++; if(count > size/2) ret |= mask; mask <<= 1; } return ret; }
单号三
给定一个整数数组,除了一个元素外,每个元素都会出现三次。找到那一个。(仍然可以通过位计数来轻松解决这种类型)。但我们将通过解决它digital logic design
解
//inspired by logical circuit design and boolean algebra; //counter - unit of 3; //current incoming next //a b c a b //0 0 0 0 0 //0 1 0 0 1 //1 0 0 1 0 //0 0 1 0 1 //0 1 1 1 0 //1 0 1 0 0 //a = a&~b&~c + ~a&b&c; //b = ~a&b&~c + ~a&~b&c; //return a|b since the single number can appear once or twice; int singleNumber(vector<int>& nums) { int t = 0, a = 0, b = 0; for(int i = 0; i < nums.size(); ++i) { t = (a&~b&~nums[i]) | (~a&b&nums[i]); b = (~a&b&~nums[i]) | (~a&~b&nums[i]); a = t; } return a | b; } ;
字长的最大值
给定一个字符串数组单词,找到两个单词不共用字母的长度(单词[i])*长度(单词[j])的最大值。你可以假设每个单词只包含小写字母。如果不存在这样的两个单词,则返回0。
示例1:
给定[“abcw”,“baz”,“foo”,“bar”,“xtfn”,“abcdef”]
返回16
这两个单词可以是“abcw”,“xtfn”。
例2:
给定[“a”,“ab”,“abc”,“d”,“cd”,“bcd”,“abcd”]
返回4
这两个单词可以是“ab”,“cd”。
示例3:
给定[“a”,“aa”,“aaa”,“aaaa”]
返回0
没有这样的单词。
解
由于我们要非常频繁地使用单词的长度,我们要比较两个单词的字母,检查它们是否有一些共同的字母:
使用int数组预先存储每个单词的长度,减少频繁的测量过程;
因为int有4个字节,32位类型,并且只有26个不同的字母,所以我们可以用一个字来表示字中字母的存在。
int maxProduct(vector<string>& words) { vector<int> mask(words.size()); vector<int> lens(words.size()); for(int i = 0; i < words.size(); ++i) lens[i] = words[i].length(); int result = 0; for (int i=0; i<words.size(); ++i) { for (char c : words[i]) mask[i] |= 1 << (c - 'a'); for (int j=0; j<i; ++j) if (!(mask[i] & mask[j])) result = max(result, lens[i]*lens[j]); } return result; }
注意
左移(或右移)后的结果太未定义
对负值进行右移操作是不确定的
右移操作数应该是非负的,否则结果是不确定的
&和| 运算符的优先级低于比较运算符
集
所有子集
位操作的一大优点是迭代遍历N元集的所有子集是微不足道的:每个N位值表示一个子集。更好的if A is a subset of B then the number representing A is less than that representing B是,这对于一些动态编程解决方案很方便。
也可以迭代特定子集的所有子集(用位模式表示),前提是您不介意以相反的顺序访问它们(如果这存在问题,请在生成时将它们放入列表中,然后向后走)。这个技巧类似于在数字中找到最低位的技巧。如果我们从一个子集中减去1,那么最低设置元素被清除,并且每个较低元素被设置。但是,我们只想设置超集中的较低元素。所以迭代步骤就是了i = (i - 1) & superset。
vector<vector<int>> subsets(vector<int>& nums) { vector<vector<int>> vv; int size = nums.size(); if(size == 0) return vv; int num = 1 << size; vv.resize(num); for(int i = 0; i < num; ++i) { for(int j = 0; j < size; ++j) if((1<<j) & i) vv[i].push_back(nums[j]); } return vv; }
其实有两个方法来处理这个使用recursion和iteration分别。
位集合
阿位集存储位(只有两个可能的值的元素:0或1,真或假,…)。
该类模拟一个布尔元素数组,但是为空间分配进行了优化:通常,每个元素只占用一个位(在大多数系统上,它比最小元素类型char少八倍)。
// bitset::count #include <iostream> // std::cout #include <string> // std::string #include <bitset> // std::bitset int main () { std::bitset<8> foo (std::string("10110011")); std::cout << foo << " has "; std::cout << foo.count() << " ones and "; std::cout << (foo.size()-foo.count()) << " zeros.\n"; return 0; }
LeetCode中的大神总结的:在此附上链接:https://leetcode.com/problems/sum-of-two-integers/discuss/84278/A-summary:-how-to-use-bit-manipulation-to-solve-problems-easily-and-efficiently