关于位运算的几种常见题型分析

有不少人对位运算感到陌生,一是因为我们不习惯用二进制的形式来进行计算,二是我们遇到的题目较少,并且很不熟练。可能我们遇到数组、链表等常见数据结构类的问题时做起来很顺利,哪怕是中等或者困难的题目也能完成,但是遇到关于位运算的简单题目却无从下手,下面我将分析几道leetcode上面的几道简单且经典的位运算题目,供大家做参考。

在分析题目之前,我先带大家熟悉下位运算的几种常见符号。

&:按位与运算。同1为1,否则为0。例:0&0=0;0&1=0;1&0=0;1&1=1;

|: 按位或运算。有1为1, 没1为0。 例:0|0=0;  0|1=1;  1|0=1; 1|1=1;

^: 按位异或运算。相异为1,相同为0。例:0^0=0;  0^1=1;  1^0=1; 1^1=0;

~:取反运算。 ~1=0;~0=1;

<<:左移运算符。左边的二进制位丢弃,右边补0。例:1011 左移一位后变为 10110;左移几位            就是乘2^n次方。

>>:右移运算符。正数左补0,负数左补1,右边丢弃。例:1011 右移一位后变为101,最后一位          丢弃;右移几位就是除2^n次方。

(1)给你一个整数 n,请你判断该整数是否是 2 的幂次方。如果是,返回 true ;否则,返回false 。如果存在一个整数 x 使得 n = 2^x ,则认为 n 是 2 的幂次方。

 通过分析题目,我们首先发现可以让n这个数一直除2,直到最后看是否等于1或者等于0,这不就是递归吗,我们可以很容易的写出递归的代码,代码如下:

class Solution {
public:
    bool isPowerOfTwo ( int n )
    {        
         //递归终止条件,如果n=1则返回true,2^0=1;
         if ( n == 1 )
             return true;
         if ( n == 0 )  
             return false;

         //如果n为偶数的话,则递归遍历
         if ( n % 2 == 0 )
             return isPowerOfTwo ( n / 2 );
         //否则是奇数,返回false
         else 
             return  false;
    }
};

但我们怎么利用位运算来解决本道题呢,我们通过举例发现:8的二进制形式为1000,而7的二进制形式为0111,如果题目输入的n是8的情况时,显然是返回true的。我们套用上面的按位运算,发现当8和7进行按位与运算时,最终答案竟然是0。再带入其他数字,例如16的二进制数为10000,而15的二进制数为01111,两者按位与运算同样是0。所以可以得出规律,二的倍数和二的倍数减一的按位与运算结果为0,我们得到如下代码。

class Solution {
public:
    bool isPowerOfTwo ( int n )
    {        
         //如果n为1时返回true
         if ( n == 1 )
            return true;
         if ( n < 1 )
            return false;
         //返回n&n-1的结果,如果为0则为true,否则返回false
         //特别提示:&的优先级要小于==的优先级,所以&的两边要加上括号
         return ( n & ( n - 1 ) == 0;
    }
};

(2)编写一个函数,输入是一个无符号整数(以二进制串的形式),返回其二进制表达式中数字位数为 '1' 的个数

此题同样要使用二进制位运算来解题,下面给出第一种写法:可以发现当让n&(n-1)时,n的最低位的1会变为0,我们利用这个特性,可以让n不停的和n-1进行与运算,直到n为0,下面给出我们的代码:

class Solution {
public:
    int hammingWeight ( uint32_t n )
    {        
         //用res来记录为1的个数
         int res = 0;
         //当n不为0时,一直循环
         while ( n )
         {
              //将n的最低位的1变为0,同时res加1
              n = n & ( n - 1 );
              res++;
         }
         return res;
    }
};

 我们也可以每次找到最低位的1以后,再将二进制代码右移一位,即删除掉那个1,然后依次循环上述过程,找到所有的1,下面给出代码:

class Solution {
public:
    int hammingWeight ( uint32_t n )
    {        
         //用res来记录为1的个数
         int res = 0;
         //因为n是32位,所以需要循环32次,每次左移一位
         for ( int i = 0; i < 32; i++ )
         {
                //如果最后一位和1相与的结果是1,则说明最后一位为1,res+1
                if ( ( n & 1 ) == 1 )
                    res++;
                //无论最低为是否为1都需右移一位
                n >>= 1;
         }
                return res;
    }
};

(3)给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

我看到这题,第一时间想到的是哈希表法,但是题目规定了时间复杂度为线性的,所以我们只能考虑位运算来解决。

回顾异或运算,我们发现异或运算有三个特性:

1、异或运算满足结合律和交换律。例:a^b^c=c^b^a=a^(b^c);

2、任何数和自身进行异或运算都为0。例:1111^1111=0;

3、任何数和0做异或运算,结果为任何数。例:1111^0=1111;

通过以上的三条特性,我们发现当异或两个相同的数时,答案正好为0,而0和其他数异或正好为其他数,这正好满足题目所给的条件。我们举例子说明:如数组元素为 [2,2,1,1,5],对数组元素依次进行异或运算,2^2^1^1^5=0^5=5;正好等于数组中单着的那个数,我们写出如下代码:

class Solution {
public:
    int singleNumber ( vector < int > & nums )
    {        
         //用c来记作返回结果
         int c = 0;
         //遍历数组所有元素,并且每个进行^运算
         for ( auto x : nums )
         {
                //按位异或每个元素
                c ^= x;
         }
                return c;
    }
};

 关于位运算的题目,掌握了几种方法,以后遇见就不会觉得难,而不知道这几种方法的做题确实是做不出来,所以二进制的题目还是要多进行积累。

  • 9
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值