异或
唯一数字!!!
所有数字异或后,出现两次的数字会变成0。结果就是只出现一次的那个数字
int singleNumber(int A[], int n) {
int result = A[0];
for(int i =1 ; i < n ;++i)
{
result = result^A[i];
}
return result;
}
唯一数2
把数字当成一个32bit的数组,记录每个bit上1总的出现次数。则对唯一出现一次的数在对应bit上
是否存在1,等于该bit上1出现的次数%3.
class Solution {
public:
int singleNumber(vector<int>& nums) {
//用一个32位数组分别记录每个bit上1出现的总次数
vector<int> count(32,0);
for(auto curr:nums){
for(int i=0;i<count.size();++i){
//不停右移,从而判断当前数的第i bit是否为1
if((curr>>i)&1)count[i]++;
}
}
int res=0;
for(int i=0;i<count.size();++i){
if(count[i]%3){ //唯一数上第i个bit上是否存在1
res|=(1<<i);
}
}
return res;
}
};
唯一数字3!!!
想到这个系列的第一个题,就是找出单个的只出现一次的字符。
这道题里,有两个数字只出现一次,且这两个数字不相同。如果我们能把这两个只出现一次的数字分开,再利用第一个题的思路,就能找处这两个只出现一次的数。
思路:把所有的数字进行一次异或,得到的是只出现了一次的两个数字的异或结果C。
这两个数字不等,因此他们的二进制必定至少1位不同,即异或结果C中为1的那位(一个数字的该位为1,另个数字的该位为0)。找出从右向左的第一个不同的位置(异或值为1的位置)。假设是
第i个bit。则根据这个bit是1还是0,我们可以把所有数字分成两堆,而这两个只出现一次的数字分别在这两堆中。因此我们可以利用第一题思路找到各自堆中只出现一次的数字。
class Solution {
public:
vector<int> singleNumber(vector<int>& nums) {
int res = 0;
for (int n : nums) res ^= n; //res中包含的是两个只出现一次数字的异或结果
int a = 0, b = 0;
int mask = 1;
while ((mask & res) == 0) mask <<= 1; //找到异或结果中为1的那个位,根据这个位可以把数字分成两类。只出现一次的数字分别在其中一类
for (int n : nums) {
if (n & mask) //把数字分成两类,并分别异或,从而找到各自类中只出现一次的那个数。
a ^= n;
else
b ^= n;
}
return {a, b};
}
};
DNA重复序列
由于总共只有四种字符,因此可以用两个bit表示所有字符,因此对于长度为10的串,可以使用20bit进行编码。相当于对字符串进行哈希。
class Solution {
inline unsigned int bitCode( char c) //利用数字编码存储字符串 标记字符串
{
if(c == 'A' )return 0;
else if(c == 'C' ) return 1;
else if(c == 'G' )return 2;
else{
return 3;
}
}
public:
vector<string> findRepeatedDnaSequences(string s) {
/*思路 从左往右扫描长度为10的字符串 每次推进1个字符 如果该字符串没有在哈希表中出现
则将该字符串作为关键字插入哈希表。 否则继续推进*/
unordered_map<int, bool> hashMap;
vector<string> result;
int currCode = 0x00000000;
//初始化字符串滑动窗
if(s.size() <= 10) return result;
for(int i = 0 ; i < 10 ; i++)
{
currCode <<=2;
currCode += bitCode(s[i]);
}
currCode = currCode & 0x000FFFFF;
hashMap[currCode] = false ;
for(int i = 10 ; i < s.size() ; ++i )
{
currCode <<=2;
currCode += bitCode(s[i]);
currCode &= 0x000FFFFF; //利用20个bit位 每2位代表一个字符来表示整个字符串
auto it=hashMap.find(currCode);
if(it != hashMap.end()){
if(it->second == false){
it->second=true;
result.push_back(s.substr(i-10+1,10));
}
}else{
hashMap[currCode] = false;
}
}
return result;
}
};
区间AND操作
由于只有所有数字在某bit为1,该bit最终相与后才能为1. 又因为数字是连续的,因此同为1的bit都在左侧。
如一个范围[26, 30],它们的二进制如下:
11010 11011 11100 11101 11110
我们只要写代码找到左边公共的部分即可。而较小的数m左边连续的1一定<n左边连续的1的个数。
因此,我们可以对m n每次向右移一位,直到m和n相等,记录下所有平移的次数i,然后再把m左移i位即为最终结果
class Solution {
public:
int rangeBitwiseAnd(int m, int n) {
int i = 0;
while (m != n) {
m >>= 1;
n >>= 1;
++i;
}
return (m << i);
}
};
bit1的个数
是DP与位操作结合的问题。
观察以下规律,可以看到某个数的1的个数=消除最左侧bit 1后得到的数里1的个数+1
因此可以使用DP进行递推。
其中消除最左侧1的个数可以通过记录≤当前数里最后一个2的N次方。从而进行取反相与操作。
0000 --》初始条件
0001 --》初始条件
0010 --> 消除bit 1 所得数字的1的个数+1–》消除2^1中的1
0011 --> 消除bit 1 所得数字的1的个数+1–》消除2^1中的1
0100 --> 消除bit 2 所得数字的1的个数+1–》消除2^2中的1
0101 --> 消除bit 2 所得数字的1的个数+1–》消除2^2中的1
0110 --> 消除bit 2 所得数字的1的个数+1–》消除2^2中的1
0111 --> 消除bit 2 所得数字的1的个数+1–》消除2^2中的1
class Solution {
public:
vector<int> countBits(int num) {
vector<int>dp(num+1,0);//dp[i]为i数字中bit 1的数字个数
dp.resize(num+1,0);
dp[0]=0;
if(num>=1) dp[1]=1;
if(num<=1) return dp;
int _2exp = 2;
for(int i=2;i<=num;++i){
if(i>=(_2exp<<1)){ //更新离当前数字i最接近且<i的2的整数幂数
_2exp<<=1;
}
dp[i] = dp[(~(_2exp))&i]+1;//消除最左侧1.递推到子问题+1
}
return dp;
}
};