您的位置:首页 > 其它

位操作来轻松高效的解决问题

2018-03-12 10:45 211 查看

维基

位操作是通过算术操作位或其他短于数据的数据段的操作。需要位操作的计算机编程任务包括低级设备控制,错误检测和纠正算法,数据压缩,加密算法和优化。对于大多数其他任务,现代编程语言允许程序员直接使用抽象而不是代表抽象的位。进行位操作的源代码使用按位操作: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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  LeetCode