LeetCode-位运算

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;
    }

收获与体会

  1. xor表示异或,任何数与0异或结果为他的本身,任何数与自己异或结果都为0(没有不同的位)
  2. 整个数组异或,出现偶数次的元素结果都为0了,剩下出现奇数次元素的异或
  3. 以下三种方式都能得到,只有sum最右一个1为1的数,
    (1010)->(0010)
 sum&(-sum) 		负数按补码存,相当于按位取反再加1
 (~sum+1)&sum
 ~(sum-1)&sum
  1. 偶数二进制最后一位都为0,奇数最后一位都为1
  2. 位运算符优先级很低,使用时最好加括号
  3. 负数取绝对值时要注意负值不为int最小值,否则会超int最大范围
  4. n 和 n−1 做与运算,会把最后一个 1 的位变成 0
  5. 当a & b的结果是负数时,左移就会造成符号位的溢出,所以需要转换为unsigned int来避免可能出现的左移越界行为。
  6. 负数右移不一定为0,符号位不动
  7. N & (1<<k),可以判断第k位是否为1(k从0开始)
  8. 0x55555555奇数位都为1(0101),0xaaaaaaaa偶数位都为1(1010)
  9. 遍历位数
int bit=32;
while(bit--){
	n=num&1;
	num>>=1;
}
  1. int类型负数右移高位补1,所以负数右移31位是-1,1 11111111111111111
  2. int 正数最大值2147483647,负数最小值-2147483648
  3. 和1与,和0或,都是数字本身
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值