常见位运算

常见位运算题目

常用的位运算符号包括:

  • “∧”按位异或
  • “&”按位与
  • “|”按位或
  • “∼”取反
  • “<<” 算术左移 是带符号左移
  • “>>” 算术右移 是带符号右移,保持符号不变

逻辑左移=算数左移,右边统一添0
逻辑右移,左边统一添0
算数右移,左边添加的数和符号有关
C/C++语言中逻辑右移和算数右移共享同一个运算符>>。
编译器决定使用逻辑右移还是算数右移,根据的是运算数的类型。
如果运算数类型是unsigned则采用逻辑右移,而signed则采用算数右移。
对于signed类型的数据,如果需要使用算数右移,或者unsigned类型的数据需要使用逻辑右移,都需要进行类型转换。
(unsigned int)a >> 1
(int)a >> 1

  • n & (n - 1) 可以去除 n 的位级表示中最低的那一位
  • n & (-n)可以获取n的最低一位非0位
    (a & (-a) 可以获得a最低的非0位 ,比如a的二进制是 ???10000,取反就是???01111,加1就是???10000。前面?的部分是和原来a相反的,相与必然都是0,所以最后整体相与的结果就是00000010000。)

位运算基础问题

461、汉明距离
两个数字之间的汉明距离 = 他们的二进制不同位置的数目

class Solution {
public:
    int hammingDistance(int x, int y) {
        int ans = 0;
        //使用位运算
        int diff = x ^ y;
        while(diff){
            ans += diff & 1;
            diff = (unsigned)diff>>1;
            //diff >>= 1;
        }
        return ans;

    }
};

注意要 diff = diff>>1 或者 diff >>= 1;
不然diff的值就不会变更

190、颠倒二进制位
给了一个32位的无符号数,将其前后颠倒后返回。

解法:可以将写一个32位的循环,每个循环中:
将ret左移一位,将当前的n的最后一位赋值给ret的最后一位,将n右移一位。

class Solution {
public:
    uint32_t reverseBits(uint32_t n) {
        uint32_t ret = 0;
        for(int i = 0;i<32;i++){
            ret <<= 1;
            ret += n&1;
            n >>= 1;
        }
        return ret;
    }
};

136、只出现一次的数字

给了一个非空整数数组,其中只有一个元素只出现了一次,其他的元素都出现了两次。找出只出现了一次的元素

解答:出现了两次的都可以消除掉,最后只留下那个出现了一次的。

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int ret = 0;
        for(int num:nums){
            ret = ret ^ num;
        }
        return ret;

    }
};

268、丢失的数组
是只出现一次的数字的改版,给了一个含n个数字的数组,求从0-n中它缺少哪一个数。
解答:可以利用只出现一次的数字的思路,如果把这个数组与0-n的所有数取^,那么其他的会被去掉。

class Solution {
public:
    int missingNumber(vector<int>& nums) {
        if(nums.size()==0)return 0;
        int ret = 0;
        for(int i = 1;i<=nums.size();i++){
            ret = ret ^ i;
        }
        for(int t:nums){
            ret = ret ^ t;
        }
        return ret;
    }
};

693、交替位二进制数
给了一个n,需要判断这个n的二进制表示中是否是交替的0和1组成。

class Solution {
public:
    bool hasAlternatingBits(int n) {
        //把数字的每一位和后一位相异或,只有一直为1 才是满足条件的。
        int t = 0;
        while(n!=0){
            t = n&1;
            n = n>>1;
            if(!(t^(n&1)))return false;
        }
        return true;
    }
};

二进制特性

可以利用二进制特性,把二进制运算用在一些题目上,如获取这个数组的全部子集。

342、4的幂
给一个整数,判断它是否是4的幂

解答:注意到2的幂应该是2进制里面只有一个1,4的幂在此基础上还需要1所在的位置为奇数。

class Solution {
public:
    bool isPowerOfFour(int n) {
        //注意输入为整数,因此不用考虑小数的情况,4^x中的x应该为正数。
        //4^0 = 00001,4^1 = 0100,4^2 = 010000,每增加一个,这个1要往左移动一位。,因此1只在奇数位置出现-在所有偶数地方不出现。
        //4的幂 = 2的幂(数字位只有一个1)+ (只有奇数位置为1)
        if(n>0&&((n&(n-1))==0)&&((n&0xaaaaaaaa)==0))return true;
        return false;
        //注意&的优先级比==低
    }
};

判断是否为2的幂可以通过n&(n-1),这个操作可以去掉n的最后一位的值(前提是正数)。
因此如果是2的幂,那么去掉这一位后就没有了,n&(n-1)==0。

318、最大单词长度乘积
给了一个单词数组,返回其中两个没有重复字符的单词的乘积最大值。

解题:问题需要如何快速的比较两个字符串之间是否有重复字符,可以为每个字符串设置一个int值,其转化为的二进制中的每一位,表示字符串中是否包含字母表中的该字母。
使用vector存储每个字符串的长度、以及该字符串对应的int值

class Solution {
public:
    int maxProduct(vector<string>& words) {
        //特判
        if(words.size()<=1)return 0;
        //快速比较两个字符串是否有重复的数字
        //可以使用一个26位的二进制,表示每个字母他是否拥有,若两个字符串对应的二进制&值为0,则没有公共字母
        vector<int> len;
        vector<int> mask;
        for(string word:words){
            len.push_back(word.size());
            //转换为二进制后,a在最后一位
            int mask_tmp = 0;
            for(char c:word){
                mask_tmp = mask_tmp | 1<<(c-'a');
            }
            mask.push_back(mask_tmp);
        }
        int ret = 0;
        for(int i = 0;i< words.size()-1;i++){
            for(int j = i+1;j<words.size();j++){
                if((mask[i]&mask[j])==0){
                    ret = max(ret,len[i]*len[j]);
                }
            }
        }
        return ret;

    }
};

338、比特位计数
给了一个n,返回一个数组,其中包含了从0-n的所有数字的二进制所包含的1的个数。

解题:1、暴力解题,一个一个数字计算过去。
2、使用dp的方法,获取每个位置值的关系
如果一个数的最后一位是1,那么它的1个数应该是上一个数1个数+1,否则的话应该等于它右移一位的值(把这个1移掉后值是相同的)

class Solution {
public:
    vector<int> countBits(int n) {
        //方法1:eng算
        // vector<int> ret(n+1,0);
        // for(int i =0;i<=n;i++){
        //     unsigned cur = i;
        //     int count = 0;
        //     while(cur!=0){
        //         count += cur&1;
        //         cur = cur>>1;
        //     }
        //     ret[i] = count;

        // }
        // return ret;
        //方法二:使用dp
        vector<int> ret(n+1,0);
        ret[0] = 0;
        if(n==0)return ret;
        ret[1] = 1;
        if(n==1)return ret;
        for(int i =2;i<=n;i++){
            if((i&1)==1){
                ret[i] = ret[i-1]+1;
            }else{
                ret[i] = ret[i>>1];
            }
        }
        return ret;

    }
};

476、数字的补数
给了一个正整数,求它从第一位非0位开始往后的所有位的二进制值取反。

解法:
想要取反,则需要与1取异或,但是需要注意的是只有从第一位二进制非0位开始取反,那么需要将1的个数和二进制有值的地方对齐。然后进行异或。

class Solution {
public:
    int findComplement(int num) {
        //需要找到与num长度相同的二进制的全1值
        unsigned mask = -1;
        //注意需要使用unsigned,不然会溢出
        while((mask & num) > 0)
        {
            mask <<= 1;
        }
        mask = ~mask;
        return num ^ mask;

    }
};

注意:想要找到长度与num相同的全为1的串,可以由前面全为1的数各个位全部取反获得。二进制的取反操作~。
获得前面全为1的数可以通过不断的左移动,看是否与当前数相与 =0 如果说等于0的话,说明此时的这个移位得到的数已经满足条件了。

260、只出现一次的数字III
给了一个数组,其中只有两个元素只出现了一次,其他的元素都出现了两次。

解答:思路是想办法把这两个元素放到不同的数组里面,希望把他们拆分为两个不同的数组,分别进行处理。
拆分的方法就是首先取所有的异或,就可以获得所有数字的异或值,然后找到最后一位值为1的(用mask & -mask获得),然后通过此来把两个只有一个的元素分到两个不同的分组中去。

class Solution {
public:
    vector<int> singleNumber(vector<int>& nums) {
        //首先找到这两个元素不同的最低的不同位
        unsigned ret = 0;
        for(int t:nums){
            ret = ret ^ t;
        }
        unsigned mask = ret & (-ret);
        vector<vector<int>> vec(2,vector<int>(nums.size(),0));
        for(int t :nums){
            if((mask & t)==0){
                vec[0].push_back(t);
            }else{
                vec[1].push_back(t);
            }
        }
        
        vector<int> ans;
        //分别对每一组进行求解
        int tmp = 0;
        for(int t: vec[0]){
            tmp = tmp ^ t;
        }
        ans.push_back(tmp);
        tmp = 0;
        for(int t: vec[1]){
            tmp = tmp ^ t;
        }
        ans.push_back(tmp);
        return ans;


    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值