一、异或
1、hot100 - 136. 只出现一次的数字(一个元素出现一次,其余出现两次)
题目:https://leetcode-cn.com/problems/single-number/
- 题解:全员异或
- 时间复杂度:O(n)
- 空间复杂度:O(1)
class Solution {
public:
int singleNumber(vector<int>& nums) {
int res = nums[0];
for(int i=1; i<nums.size(); i++){
res ^= nums[i];
}
return res;
}
};
2、剑指 Offer 56 - I. 数组中数字出现的次数(两个元素出现一次,其余出现两次)
题目:https://leetcode-cn.com/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-lcof/
- 解法:
- 如果只有一个数字只出现一次,其他数字都出现两次,进行全员异或即可;
- 若有两个数字出现一次,其他数字都出现两次,需要分组,分组条件:
- 两个独特的数字分成不同的组;
- 相同的数字分成相同的组。
- 时间复杂度:O(n)
- 空间复杂度:O(1)
class Solution {
public:
vector<int> singleNumbers(vector<int>& nums) {
int ret = 0;
//ret为两个只出现一次的数字的异或结果
for(int i: nums) ret ^= i;
int div = 1;
//找到ret中第一个二进制为1的位置div
while((div&ret)==0) div <<= 1;
int a = 0, b = 0;
for(int i: nums){
//div与第一个只出现一次的数字&为1时;
//div与另一个只出现一次的数字&必为0;
//相同的数字在div位的二进制值必相同;
//可以将相同数分在同一组内。
if((div & i)) a ^= i;
else b ^= i;
}
return vector<int>() = {a, b};
}
};
二、一般位运算
1、剑指 Offer 56 - II. 数组中数字出现的次数 II(一个元素出现一次,其余出现三次)
题目:https://leetcode-cn.com/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-ii-lcof/
- 解法:
- 如果一个数字出现三次,那个它的二进制表示的每一位(0或者1)也出现三次;
- 如果把所有出现三次的数字的二进制表示的每一位都分别加起来,那么每一位的和都能被3整除;
- 把数组中所有数字的二进制表示的每一位都加起来;
- 如果某一位的和能被3整除,那么只出现一次的数字二进制表示中对应的那一位是0;
- 否则是1。
- 时间复杂度:O(n)
- 空间复杂度:O(1)
class Solution {
public:
int singleNumber(vector<int>& nums) {
int data[32] = {0};
for(int num: nums){
long int div = 1; //防止溢出使用long int
for(int i=0; i<32; i++){
if(div & num) data[i]++;
div <<= 1;
}
}
int res = 0;
for(int i=31; i>=0; i--){
res <<= 1;
if(data[i]%3) res |= 1;
}
return res;
}
};
2、Leetcode - 191. 位1的个数【蔚来】
题目:https://leetcode.cn/problems/number-of-1-bits/solution/
方法一:循环检查二进制位
- 时间复杂度:O(k),其中k是int 型的二进制位数,k=32。我们需要检查n的二进制位的每一位,一共需要检查32 位。
- 空间复杂度:O(1)。
class Solution {
public:
int hammingWeight(uint32_t n) {
int ret = 0;
uint32_t flag = 1;
for (int i = 0; i < 32; i++) {
if (n & flag) {
ret++;
}
flag <<= 1;
}
return ret;
}
};
方法二:位运算优化
- 观察这个运算:n&(n−1),其运算结果恰为把n的二进制位中的最低位的1变为0之后的结果。
- 如:6 & (6−1)=4,6=(110)2 ,4=(100)2 ,运算结果4即为把6的二进制位中的最低位的1变为0之后的结果。
- 这样可以利用这个位运算的性质加速我们的检查过程,在实际代码中,不断让当前的n与n−1做与运算,直到n变为0即可。因为每次运算会使得n的最低位的1被翻转,因此运算次数就等于n的二进制位中1的个数。
- 时间复杂度:O(logn)。循环次数等于n的二进制位中1的个数,最坏情况下n的二进制位全部为1。我们需要循环logn次。
- 空间复杂度:O(1),我们只需要常数的空间保存若干变量。
class Solution {
public:
int hammingWeight(uint32_t n) {
int ret = 0;
while (n) {
n &= n - 1;
ret++;
}
return ret;
}
};
三、Brian Kernighan 算法
概述:
- 该算法可以被描述为这样一个结论:记f(x)表示x和x-1进行与运算所得的结果(即f(x)=x&(x-1)),那么f(x)恰为x删去其二进制中最右侧的1的结果。
1、hot100 - 338. 比特位计数
题目:https://leetcode-cn.com/problems/counting-bits/
方法一、Brian Kernighan 算法
- 解法:
- 令x = x&(x-1),该运算将x的二进制表示的最后一个1变成0;
- 对x重复该操作,直到x变成0,则操作次数即为x的一比特数。
- 时间复杂度:O(nlogn)
- 空间复杂度:O(1)
class Solution {
public:
int count(int x){
int ans = 0;
while(x){
x &= (x-1);
ans++;
}
return ans;
}
vector<int> countBits(int n) {
vector<int> res = {0};
for(int i=1; i<=n; i++){
res.push_back(count(i));
}
return res;
}
};
方法二、动态规划-最高有效位
- 时间复杂度:O(n)
- 空间复杂度:O(1)
2、hot100 - 461. 汉明距离
题目:https://leetcode-cn.com/problems/hamming-distance/
方法一、内置位计数功能函数
- 时间复杂度:不同语言的实现方法不一,可以近似认为其时间复杂度为O(1)
- 空间复杂度:O(1)
class Solution {
public:
int hammingDistance(int x, int y) {
//__builtin_popcount():c/c++中计算一个无符号整数有多少个位1
return __builtin_popcount(x^y);
}
};
方法二、异或运算
-
解法:
- 记s=x^y(x异或y);
- 不断的检查s的最低位;
- 如果最低位为1,那么计数器+1;
- 之后令s整体右移一位(这样s的最低位将被舍去,原本的次低位就变成了新的最低位);
- 一直重复以上过程,直到s=0为止。
-
时间复杂度:O(logC)。其中C是元素的数据范围,在本题中 logC=log2^31=31
-
空间复杂度:O(1)
class Solution {
public:
int hammingDistance(int x, int y) {
int s = x^y, res = 0;
while(s){
res += s&1;
s >>= 1;
}
return res;
}
};
方法三、Brian Kernighan 算法
- 时间复杂度:O(logC),其中C是元素的数据范围,在本题中 logC=log2^31=31。
- 空间复杂度:O(1)
class Solution {
public:
int hammingDistance(int x, int y) {
int s = x^y, res = 0;
while(s){
s &= (s-1);
res++;
}
return res;
}
};