1. 题号268. 丢失的数字
让所有数组元素与0-n整数做异或,只有丢失数字出现奇数次,结果就为丢失数字
sum初值不一定非要为0,此处为n方便遍历
也可以求0到n和,减去当前数组和
int missingNumber(vector<int>& nums) {
int missing = nums.size();
for (int i = 0; i < nums.size(); i++) {
missing ^= i ^ nums[i];
}
return missing;
}
2. 题号645. 错误的集合
有一个重复元素,有一个缺失元素
让整个数组与1-n整数做异或,只有重复元素和缺失元素出现奇数次,所得结果为这两个数的异或
接下来获得 t=(1010)->(0010)这个数,异或说明重复元素和缺失元素,这位不相同,根据 num[i]&t 分为两组,这样两组中又分别只有一个出现奇数次元素,进行异或即为重复元素和缺失元素
但我们无法区分哪个是缺失哪个是重复,再遍历数组,没有在数组中出现的元素为缺失元素,返回两个元素即可,太棒了!
vector<int> findErrorNums(vector<int>& nums) {
int sum=0,n=nums.size();
for(int i=0;i<n;i++){
sum^=(i+1)^nums[i];
}
int t=sum&(-sum),x1=0,x2=0;
for(int i=0;i<n;i++){ 整数分组并异或
if(nums[i]&t){
x1^=nums[i];
}else{
x2^=nums[i];
}
}
for(int i=1;i<n+1;i++){ 数组元素分租并异或
if(i&t){
x1^=i;
}else{
x2^=i;
}
}
bool flag=true;
for(int i=0;i<n;i++){ 分辨谁是缺失元素
if(nums[i]==x1){
flag=false;
}
}
vector<int> ans;
if(flag){
ans={x2,x1};
}else{
ans={x1,x2};
}
return ans;
}
3. 题号231. 2的幂
2的幂只有一位为1
可以用x&(-x)只保留最右边的1,如果他还等于本身,则他是2的幂
注意将0特判
bool isPowerOfTwo(int n) {
if(n==0) return false;
long x=n;
return (x&(-x))==x;
}
(x - 1) 代表了将 x 最右边的 1 设置为 0,并且将较低位设置为 1。
100->011
再使用与运算就会把最右边的1之后的位都置为0
100 & 011 = 0
因为2的幂只有一个1,所以进行上述操作后会为0
注意将0特判
bool isPowerOfTwo(int n) {
if(n==0) return false;
long x=n;
return (x&(x-1))==0;
}
4. 题号371. 两整数之和
用&计算进位,只有两个1才进位
用^计算两数相加,只有0和1相加才为1
先^计算两者的进位,然后左移一位,这样就相当于进位了,直接加就好,之后直接加进位数就好,直接需要转无符号整形进位,否则会超范围(负数第一个数为1)
直到进位数为0跳出循环
int getSum(int a, int b) {
while(b){
int carry=(unsigned int)(a&b)<<1; 计算进位
a^=b; 直接b相加
b=carry; 让b等于进位
}
return a;
}
5. 题号405. 数字转换为十六进制数
建立一个包含所有十六进制字符的字符串
因为负数右移不一定会为0,所以用count记录,要小于八
也可以转成无符号整数
索引与15(15后四位为1111)做与运算,res=hex[num&15]+res,不能写+=,要让新产生的高位数在前面,然后每次右移4位,直到num为0
string toHex(int num) {
string hex="0123456789abcdef";
string res="";
int count=0;
if(!num) return "0";
while(num && count<8){
res=hex[num&15]+res;
num>>=4;
count++;
}
return res;
}
常规做法,转换为无符号整数,索引为对16求余,然后每次除16,直到num为0
string toHex(int num) {
string hex="0123456789abcdef";
string res="";
int count=0;
if(!num) return "0";
unsigned int a=num;
while(a){
res=hex[a%16]+res;
a/=16;
count++;
}
return res;
}
6. 题号342. 4的幂
4的幂都是2的幂,也只有一个1,但4的幂1在奇数位上,如100
0xaaaaaaaa,为1010…所有1都在偶数为上,4的幂&它为0
那么一个数大于0,只有一个1,并且在奇数位上,就是4的幂
4的幂对3取余等于1,也可以利用这个性质
bool isPowerOfFour(int n) {
return (n>0)&&((n&(n-1))==0)&&((n&0xaaaaaaaa)==0);
return (n>0)&&((n&(n-1))==0)&&((n%3)==1);
}
7. 题目1356. 根据数字二进制下 1 的数目排序
__builtin_popcount(x),获取x中1的个数
根据两个东西排序,可以考虑放到pair里,比较两个pair
vector<int> sortByBits(vector<int>& arr) {
auto proj = [](int x) {
return pair{ __builtin_popcount(x), x };
};
sort(begin(arr), end(arr),
[&](int a, int b) { return proj(a) < proj(b); });
return arr;
}
8. 题号762. 二进制表示中质数个计算置位
绝了,不需要遍历找质数,因为int位数一共32位,把32以内的质数都列出来就好,本题给了数字的上限,一共不超过2的20次方,所以把20以内的质数列出来就好,时间复杂度为1
int countPrimeSetBits(int L, int R) {
int x=0,ans=0;
for(int i=L;i<=R;i++){
int a=i,x=0;
while(a){ 找数中1的个数
a&=(a-1);
x++;
}
if(x == 2 || x == 3 || x == 5 || x == 7 || 判断是否质数
x == 11 || x == 13 || x == 17 || x == 19){
ans++;
}
}
return ans;
}
9. 面试题 05.01. 插入
先把 [i,j]的位都置为0,然后用异或把m值赋值过去
int insertBits(int N, int M, int i, int j) {
for(int k=i;k<=j;k++){
if(N & (1<<k)){ 判断k位是否为0
N-=(1<<k); 将k为置为0
}
}
return N^(M<<i); 将m右移i位加到n上
}
10.面试题 05.07. 配对交换
先把奇数位提取出来<<1到偶数位,偶数位提取出来>>1到奇数位,然后进行或运算,就完成了奇数位与偶数位的交换
int exchangeBits(int num) {
return ((num&0x55555555)<<1)|((num&0xaaaaaaaa)>>1);
}
11. 题号190. 颠倒二进制位
创造一个ans,把n最低位(ans&1)放到ans中,ans每次左移(先来的就是高位),n每次右移,保证与1取最低位
最后ans就是颠倒后的结果
uint32_t reverseBits(uint32_t n) {
uint32_t ans=0,i=32;
while(i--){
ans<<=1;
ans+=n&1;
n>>=1;
}
return ans;
}
12. 面试题 17.01. 不用加号的加法
进位用&计算,只有两个1才能进位,然后左移一位相当于进位了
求和用^计算,0和1相加才为0
直到进位数为0就计算好了
int add(int a, int b) {
unsigned int carry=0;
while(b){
carry=(unsigned int)(a&b)<<1; 储存进位
a^=b; 储存和
b=carry;
}
return a;
}
13. 面试题 05.03. 翻转数位
遍历位数,遇到1,当前连续数量加一,第一次遇到零,由于pre是0,相当于没有减,把pre赋值为cur+1(当前元素变为0损失的位数),以此类推直到遍历位数结束
注意以后每次遇到0都是相当于第二次
1234,每一次遇0相当于从34区间移动到23区间
int reverseBits(int num) {
int maxLen = 0, preLen = 0, curLen = 0, bits = 32;
while(bits--){
if((num&1)==0){
curLen-=preLen;
preLen=curLen+1;
}
curLen++;
maxLen=max(curLen,maxLen);
num>>=1;
}
return maxLen;
}
14. 面试题 16.07. 最大数值
防止数据溢出用long储存(如最大正数减负一)
根据long右移63为,正数为0,负数为-1的特点返回 k*a+(!k)*b
int maximum(int a, int b) {
long c=a,d=b;
int k=1+((c-d)>>63);
return k*a+(!k)*b;
或k不加一
return (1 + k) * a - b * k;
}
15. 题号201. 数字范围按位与
数字再相加过程中,会出现100000,这种情况,让最后几位都为0
所以只要找到m和n公共前缀就好
int rangeBitwiseAnd(int m, int n) {
int mask=1<<30,ans=0; 让mask在除符号位的最高位
while(mask>0 && (m&mask)==(n&mask)){ 和1与保留原位,并判断是否相等
ans = ans|(m&mask); 将相同前缀与0或保留到ans中
mask>>=1; 标志位右移直到小于0
}
return ans; 返回最终结果
}
16. 题号477. 汉明距离总和
遍历数组需要阶乘时间复杂度
所以把每个位有多少个不同的位记录下来,最后该位的汉明距离总和为
k *(n-k),把每一位的汉明距离相加就是汉明距离的总和
int totalHammingDistance(vector<int>& nums) {
if(nums.empty()){
return 0;
}
vector<int> cnt(31,0);
int n=nums.size(),ans=0;
for(int num:nums){
int i=0;
while(num>0){
cnt[i]+=(num&1);
i++;
num>>=1;
}
}
for(int k:cnt){
ans+=k*(n-k);
}
return ans;
}
收获与体会
- xor表示异或,任何数与0异或结果为他的本身,任何数与自己异或结果都为0(没有不同的位)
- 整个数组异或,出现偶数次的元素结果都为0了,剩下出现奇数次元素的异或
- 以下三种方式都能得到,只有sum最右一个1为1的数,
(1010)->(0010)
sum&(-sum) 负数按补码存,相当于按位取反再加1
(~sum+1)&sum
~(sum-1)&sum
- 偶数二进制最后一位都为0,奇数最后一位都为1
- 位运算符优先级很低,使用时最好加括号
- 负数取绝对值时要注意负值不为int最小值,否则会超int最大范围
- n 和 n−1 做与运算,会把最后一个 1 的位变成 0
- 当a & b的结果是负数时,左移就会造成符号位的溢出,所以需要转换为unsigned int来避免可能出现的左移越界行为。
- 负数右移不一定为0,符号位不动
- N & (1<<k),可以判断第k位是否为1(k从0开始)
- 0x55555555奇数位都为1(0101),0xaaaaaaaa偶数位都为1(1010)
- 遍历位数
int bit=32;
while(bit--){
n=num&1;
num>>=1;
}
- int类型负数右移高位补1,所以负数右移31位是-1,1 11111111111111111
- int 正数最大值2147483647,负数最小值-2147483648
- 和1与,和0或,都是数字本身