位运算的奇淫技巧
位运算是什么东东?到底重不重要?作者可以毫不夸张地告诉大家位运算是所有程序员都必须了解的知识,在各大互联网公司的面试中常常会遇到位运算问题,那究竟什么是位运算呢?
位运算:
程序中的所有数在计算机内存中都是以二进制的形式储存的。位运算就是直接对整数在内存中的二进制位进行操作。
面试中经常遇到位运算问题:
异或^ 运算符满足交换律和结合律,并且任何两个相同的数异或等于0(a^
a=0),任何数与0异或都等于它本身(a^0=a).
leetcode第136题. 只出现一次的数字
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
示例 :
输入: [4,1,2,1,2]
输出: 4
class Solution {
public:
int singleNumber(vector& nums) {
int temp = nums[0];
for(int i = 1;i < nums.size();i++)
temp ^= nums[i];
return temp;
}
};
这道题利用上面异或两个相同的数等于0的性质,并且异或满足交换律,那么把这些数全部异或1^ 1^ 2 ^ 2 ^ 4=0^ 4=4。是不是觉得简单?那接下来再来看一道变形题:
找出成对出现的数字
1-1000这1000个数放在含有1001个元素的数组中,只有唯一的一个元素值重复,其他均只出现一次,每个数组元素只能访问一次,设计一个算法,将它找出来;不用辅助存储空间,能否设计一个算法实现?
遇到这样的问题,常规的思路是统计数组中数字出现的次数,出现次数为2的数即为答案。但这里讲到了位运算,那用位运算可不可以解答呢,答案是肯定的。这道题其实也是一道脑筋急转弯的题目,首先我们得想办法消去1-1000中没有重复的数字,那么我们就可以将数组元素全部异或后再去异或1-1000,将没有重复的数字配对成两个,最后剩下那个数就是我们要的了。
class Solution {
public:
int singleNumber(vector& nums) {
int temp = nums[0];
for(int i = 1;i < 1001;i++)
temp ^= nums[i];
for(int i = 1;i <= 1000;i++)
temp ^= i;
return temp;
}
};
^运算符实现交换两个数
常规的操作需要借助第三个临时变量来实现交换,而使用^运算可以不需要临时变量,是不是很神奇?
//普通操作
void swap(int &a, int &b)
{
a = a + b;
b = a - b;
a = a - b;
}
//位与操作
void swap(int &a, int &b)
{
a ^= b;
b ^= a;
a ^= b;
}
&运算符判断奇偶数
根据二进制的最后一位是 0 还是 1 来决定奇偶数,为 0 就是偶数,为 1 就是奇数。一般的做法这样判断
if(n%2==1) //n为奇数
if(n%2==0) //n为偶数
现在了解&运算符后,可以这样操作
if(n&1==1) //n为奇数
if(n&1==0) //n为偶数
是不是逼格更高了,而且这样的代码运行效率远高于常规写法。
leetcode第191题 位1的个数
编写一个函数,输入是一个无符号整数,返回其二进制表达式中数字位数为 ‘1’ 的个数(也被称为汉明重量)。
示例 1:
输入:00000000000000000000000000001011
输出:3
解释:
输入的二进制串 00000000000000000000000000001011中,共有三位为'1'
class Solution {
public:
int hammingWeight(uint32_t n) {
int cnt=0;
while(n)
{
cnt += n&1;
n>>=1;
}
return cnt;
}
};
这道题统计二进制中1的个数,可以通过该数和1做&运算,每次与运算后都向右移一位,直至为0,最后返回1的个数。
>>和<<运算符来实现乘除法
数 x向右移一位,相当于将 x除以 2;
数 x向左移一位,相当于将 x乘以 2。
int x = 2;
x >> 1; //此时x=1
//移位前:0000 0000 0000 0000 0000 0000 0000 0010
//移位后:0000 0000 0000 0000 0000 0000 0000 0001
x << 1; //此时x=4
//移位前:0000 0000 0000 0000 0000 0000 0000 0010
//移位后:0000 0000 0000 0000 0000 0000 0000 0100
以后乘除2时,尽量用这种方式,效率比常规写法高很多,而且快的不是一点,重要的是还能装逼,哈哈。
补充一个知识点: 2的幂次方在二进制下,只有1位是1,其余全是0,咱们来看一道例题
leetcode第231题 2的幂
给定一个整数,编写一个函数来判断它是否是 2 的幂次方。
示例 1
输入: 1
输出: true
解释:2^0=1
示例 2:
输入: 16
输出: true
解释: 2^4= 16
class Solution {
public:
bool isPowerOfTwo(int n) {
if(n<=0)
return false;
int cnt=0;
while(n)
{
if(n&1==1)
cnt++;
n>>=1;
}
if(cnt==1)
return true;
return false;
}
};
这道题判断是否为2的幂次方,可以先统计二进制中1的个数,如果个数为1,则是2的幂次方返回true,否则返回false。
这道题还有一种解法
class Solution {
public:
bool isPowerOfTwo(int n) {
if(n<=0) return false;
return (n&(n-1))==0;//判断是否为2的整数次方
}
};
一行代码就搞定了,是不是很神奇?首先我们需要知道n&(n-1)的作用是消去n的二进制最低位上的1,如果两者相与后结果为0,则说明n的二进制中1的个数为1个,
例如8的二进制1000,7的二进制0111,二者做与运算结果为0。
8的二进制:0000 0000 0000 0000 0000 0000 0000 1000
7的二进制:0000 0000 0000 0000 0000 0000 0000 0111
两者&运算:0000 0000 0000 0000 0000 0000 0000 0000
这样一行代码就轻松判断一个数是否为2的整数次方了。是不是很简单?
接下来让我们来看一道稍微有点难度的变形题
leetcode第342题 4的幂
给定一个整数 (32 位有符号整数),请编写一个函数来判断它是否是 4 的幂次方。
示例 1:
输入: 16
输出: true
示例 2:
输入: 5
输出: false
class Solution {
public:
bool isPowerOfFour(int num) {
if(num<=0 || (num&(num-1))==1)
return false;
return num%3==1; //4的任何幂次方对3取余后等于1
}
};
首先我们得知道数字4幂的二进制类似于100,10000,1000000,那么我们可以得出结论:4的幂一定是2的幂,4的幂和2的幂一样,二进制只会出现一位1。
如果你觉得这篇文章对你有所帮助,记得点赞收藏哦.
关注公众号[程序员小陈],会通过例题讲解算法技巧以及数据结构与算法等计算机知识.