位运算专题
目录
LeetCode 231 2的幂
题目:https://leetcode-cn.com/problems/power-of-two/submissions/
给定一个整数,编写一个函数来判断它是否是 2 的幂次方。
示例 1:
输入: 1
输出: true
解释: 20 = 1
1、分析
如果我们用2进制来表示n,则如果是2的幂次方,那么它的最高位一定是1,且只有这一个1,在位运算中,有一个操作是n&-n表示n从低位数起的第一个1的位置k,最后值为2^k,例如n=6,二进制为110,则-n是n的反码加1,即001+1=010,再和n相与结果为010,所以如果是一个2的幂次方数例如8,二进制为1000,-n=0111+1=1000,最后结果正好等于本身n,所以我们可以判断(n&-n)==n?即可判断是否是2的幂次方数。
2、代码
class Solution {
public:
bool isPowerOfTwo(int n) {
return (n>0)&&((n&-n)==n);
}
};
LeetCode 762 二进制表示中质数个数计算置位
题目:https://leetcode-cn.com/problems/prime-number-of-set-bits-in-binary-representation/
给定两个整数 L 和 R ,找到闭区间 [L, R] 范围内,计算置位位数为质数的整数个数。
(注意,计算置位代表二进制表示中1的个数。例如 21 的二进制表示 10101 有 3 个计算置位。还有,1 不是质数。)
示例 1:
输入: L = 6, R = 10
输出: 4
解释:
6 -> 110 (2 个计算置位,2 是质数)
7 -> 111 (3 个计算置位,3 是质数)
9 -> 1001 (2 个计算置位,2 是质数)
10-> 1010 (2 个计算置位,2 是质数)
1、分析
由于数据不是很多,所以我们可以遍历统计L~R中每一个数中1的个数是否是质数,若是则结果加1,然后由于每个数的大小不是很大,最多20位,所以我们可以先用一个哈希表把20以内的质数存起来,方便查询。
判断一个数n的第i位是否是1:n>>i&1
class Solution {
public:
int countPrimeSetBits(int L, int R) {
unordered_set<int> primes({2,3,5,7,11,13,17,19}); //先存储20以内的质数
int res=0;
for(int i=L;i<=R;i++) //遍历L~R中的每一个数
{
int s=0;
for(int j=i;j;j>>=1) s+=j&1; //统计每一个数1的个数
if(primes.count(s)) res++;
}
return res;
}
};
LeetCode 136 只出现一次的数
题目:https://leetcode-cn.com/problems/single-number/
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
示例 1:
输入: [2,2,1]
输出: 1
1、分析
这个题其实考察的就是异或运算,把所有的数异或起来,可以消除其中一对的数,还有就是任何数和0异或,结果都是本身。所以这个我们只需要把所有数异或起来,我们就可以得到那个单独的数。
2、 代码
class Solution {
public:
int singleNumber(vector<int>& nums) {
int res=0;
for(auto x:nums) res^=x;
return res;
}
};
LeetCode 476 数字的补数
题目:https://leetcode-cn.com/problems/number-complement/
给定一个正整数,输出它的补数。补数是对该数的二进制表示取反。
注意:
给定的整数保证在32位带符号整数的范围内。
你可以假定二进制数不包含前导零位。
示例 1:
输入: 5
输出: 2
解释: 5的二进制表示为101(没有前导零位),其补数为010。所以你需要输出2。
1、分析
这里的求补数,不是我们我们之前意义上的求反码,例如5的二进制是101,那么补数就是010=2,对于前面的多个0不取反,只把已有的位数取反,所以我们需要知道当前数有几位。而且需要判断每一位是0还是1,并取反。
2、代码
class Solution {
public:
int findComplement(int num) {
int res=0,t=0;
while(num) //遍历num的每一位
{
res+=!(num&1)<<t; //把num的当前最低位取反
num>>=1;
t++;
}
return res;
}
};
LeetCode 137 只出现一次的数字II
题目:https://leetcode-cn.com/problems/single-number-ii/submissions/
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现了三次。找出那个只出现了一次的元素。
说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
示例 1:
输入: [2,2,3,2]
输出: 3
1、分析
我们可以分析每一位1的个数,当当前位1的个数是3的倍数时,那么单独的数在当前位一定是1,若统计当前位1的个数不是3的倍数,那么单独的数在当前位一定是1,这样我们就把结果加进来。
2、代码
class Solution {
public:
int singleNumber(vector<int>& nums) {
int res=0;
for(int i=0;i<32;i++) //查看每一位1的个数和
{
int s=0;
for(auto x:nums) //把每一个数在当前位计算1的个数和
{
s+=x>>i&1;
}
if(s%3) res+=1<<i; //若当前位1的个数不是3的倍数,则单独的数在当前位一定是1
}
return res;
}
};
LeetCode 260 只出现一次的数字III
题目:https://leetcode-cn.com/problems/single-number-iii/submissions/
给定一个整数数组 nums,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。
示例 :
输入: [1,2,1,3,2,5]
输出: [3,5]
1、分析
我们同样可以先把所有数异或起来,那么最终的结果就是剩下的两个不同数的异或结果,对于这个结果肯定有一位是1,因为这两个数不一样,至少在某一位不一样,那么异或的结果就会是1,现在我们需要找出其中是1的那一位,然后根据此标识,可以把数据分成两类,一类是在这一位是1的一类,另一类是在这一位不是1的一类,在这两类中分别异或那么就可以分别得出那一个不成对的数。
2、代码
class Solution {
public:
vector<int> singleNumber(vector<int>& nums) {
int s=0;
for(auto x:nums) s^=x; //两个不同数异或的结果
int k=0;
while(!(s>>k&1)) k++; //找到s中某一位是1的位置
int s2=0;
for(auto x:nums)
{
if(x>>k&1) s2^=x; //根据第k位是否是1可以分成两类,把其中一类异或起来
}
//假设s=a^b,现在s2=a,那么b=s2^s=a^a^b=b
return vector<int>({s2,s2^s});
}
};
LeetCode 371 两整数之和
题目:https://leetcode-cn.com/problems/sum-of-two-integers/
不使用运算符 + 和 - ,计算两整数 a 、b 之和。
示例 1:
输入: a = 1, b = 2
输出: 3
1、分析
我们在这里用异或^和与&运算符来解决这个问题,异或即是不进位加法,而与则可以知道那一位需要进行进位,所以我们最终的结果就是异或的结果加上进位的结果,这里可以用递归解决。
2、代码
class Solution {
public:
int getSum(int a, int b) {
if(b==0) return a;
int sum=a^b,carry=uint(a&b)<<1; //由于是进位到下一位,所以需要左移一位,uint防止负数等数据
return getSum(sum,carry);
}
};
LeetCode 201 数字范围按位与
题目:https://leetcode-cn.com/problems/bitwise-and-of-numbers-range/
给定范围 [m, n],其中 0 <= m <= n <= 2147483647,返回此范围内所有数字的按位与(包含 m, n 两端点)。
示例 1:
输入: [5,7]
输出: 4
1、分析
对于m和n,我们那彼此的二进制来说,从高位到低位依次判断,两者是否一样,当找到第一个不一样的位时,前面所有位代表的结果即是最后的结果,比如m和n是:xxx0.....和xxx1.......,那么在从m到n不断增加的过程中,一定会经过一个xxx011111到xxx100000的过程,那么后面结果与一定会是0,所以我们可以从n的低位开始,依次去掉n的最低位1,当n不在大于m时,则返回当前n的值。
2、代码
class Solution {
public:
int rangeBitwiseAnd(int m, int n) {
while(m<n)
{
n&=(n-1); //去掉低位的第一个1
}
return n;
}
};
LeetCode 421 数组中两个数的最大异或值
题目:https://leetcode-cn.com/problems/maximum-xor-of-two-numbers-in-an-array/
给定一个非空数组,数组中元素为 a0, a1, a2, … , an-1,其中 0 ≤ ai < 231 。
找到 ai 和aj 最大的异或 (XOR) 运算结果,其中0 ≤ i, j < n 。
你能在O(n)的时间解决这个问题吗?
示例:
输入: [3, 10, 5, 25, 2, 8]
输出: 28
解释: 最大的结果是 5 ^ 25 = 28.
1、分析
我们先分析对于x,我们找一个和x取异或,使结果最大的y,那么应该如何寻找了。我们肯定是找一个和x的二进制尽可能不一样的数,我们在找的时候,由于每一位的权重是不一样的,最高位权重最大,所以肯定是从高位开始找和自身不一样的数。我们在这里可以用字典树来存储每一个数的二进制,在这里的字典树每一分支只有0或1。
如图所示,假设x是25,我们现在找一个和x异或的结果最大的y,我们看x的最高位是1,从根节点出发,所以我们需要往0分支走,走到b,再看下一位还是1,所以继续往0分支走,走到c,继续看下一位是0,所以需要往1分支走,走到d,继续看下一位是0,我们需要往1分支走,但是当前分支没有1,所以只能往0走,走到e,继续看最后一位是1,所以需要往0分支走,但是当前分支没有0,所以只能走到1,即走到了5,所以使和25异或的结果最大的数是5。
2、代码
class Solution {
public:
struct Node //构造一个字典树
{
int son[2];
};
vector<Node> nodes;
int findMaximumXOR(vector<int>& nums) {
nodes.push_back(Node({0,0})); //创造根节点
for(auto x:nums)
{
int p=0;
for(int i=30;i>=0;i--)
{
int t=x>>i&1;
if(!nodes[p].son[t]) //p就是此节点的下标
{
nodes.push_back(Node({0,0}));
nodes[p].son[t]=nodes.size()-1; //son里存的就是当前下标
}
p=nodes[p].son[t];
}
}
int res=0;
for(auto x:nums)
{
int p=0,max_xor=0;
for(int i=30;i>=0;i--) //从最高位开始看
{
int t=x>>i&1;
if(nodes[p].son[!t]) //看是否有和当前位相反的分支
{
p=nodes[p].son[!t];
max_xor+=1<<i; //加上当前位
}
else
{
p=nodes[p].son[t];
}
}
res=max(res,max_xor);
}
return res;
}
};
LeetCode 477 汉明距离总和
题目:https://leetcode-cn.com/problems/total-hamming-distance/
两个整数的 汉明距离 指的是这两个数字的二进制数对应位不同的数量。
计算一个数组中,任意两个数之间汉明距离的总和。
示例:
输入: 4, 14, 2
输出: 6
解释: 在二进制表示中,4表示为0100,14表示为1110,2表示为0010。(这样表示是为了体现后四位之间关系)
所以答案为:
HammingDistance(4, 14) + HammingDistance(4, 2) + HammingDistance(14, 2) = 2 + 2 + 2 = 6.
1、分析
由于每一位都是独立的,我们可以独立开来看每一位,比如从个位开始,假设有4个数,个位分别是:0 1 0 1,那么他们之间的汉明距离总和是4,只有当其中一个是1,另一个是0时,汉明距离才会加一,所以我们可以统计出0和1的个数,然后相乘就是最后的结果。
2、代码
class Solution {
public:
int totalHammingDistance(vector<int>& nums) {
int res=0;
for(int i=0;i<=30;i++)
{
int ones=0;
for(auto x:nums)
{
if(x>>i&1) ones++; //统计每一位1的个数
}
res+=ones*(nums.size()-ones); //1的个数乘以0的个数即是当前位的汉明距离和
}
return res;
}
};
来源:CSDN
作者:烊萌
链接:https://blog.csdn.net/qq_36417014/article/details/103462948