问题描述
问题:计算A+B,不适用“+”运算符(LintCode 1.A + B Problem)
注意:A B均为32位整数,可使用比特位计算
解决思路
算法示例
代码如下:(通过LintCode测试)
class Solution {
public:
/*
* @param : An integer
* @param : An integer
* @return: The sum of a and b
*/
int aplusb(int a, int b) {
// write your code here
int s, c;
while(b != 0)
{
s = a^b;
c = (a&b)<<1;
a = s;
b = c;
}
return a;
}
};
知识整理
位运算
位运算是按照二进制进行的运算,c语言提供6个位运算操作符,这些操作符适用于整型操作数。具体6个操作符如下:
符号 | 名称 | 含义 |
---|---|---|
& | 按位与 | 两个相应的二进制位都为1,该位的结果值为1 |
| | 按位或 | 两个相应的二进制位中只要有一个为1,该位的结果值为1 |
^ | 按位异或 | 两个相应的二进制位值相同则为0,否则为1 |
~ | 取反 | 一元运算符,对一个二进制数按位取反,将0变1,将1变0 |
<< | 左移 | 将一个数的各二进制位全部左移N位,右补0 |
>> | 右移 | 将一个数的各二进制位右移N位,移到右端的低位被舍弃,无符号数高位补0 |
1.按位与 &
参与运算的两个数按照二进制位依次进行与运算,即两位都是1结果为1,否则为0。例如:3&6,换算为8位二进制数(高位补0),为
00000011&
00000110=
00000010
按位与可用于:清零、保留指定位。
清零:想将原数清零,只需要原数中所有为1的位,在新数位相应为0,两数&操作即可为00000000。
保留指定位:想保留原数的第n位,只要新数的第n位保证为1,两数&操作即可保留原数的第n位,想保留多位同理。
2.按位或 |
参与运算的两个数对应的二进制位只要有一个为1,结果为1。例如
00011001 |
01001010=
01011011
按位或可用于:将原数的某些位定值为1,只需要与它进行按位或操作的新数对应的位数为1即可。
3.按位异或 ^
参与运算的两个数对应二进制位相同为0,不同为1。例如:
00110011 ^
01000110 =
01110101
按位异或可用于:指定位翻转(与1异或)、保留原值(与0异或)、交换两数。
交换两数:交换a和b:
a=a∧b;
b=b∧a;
a=a∧b; 交换成功
4.取反 ~
二进制位1变0,0变1。
5.左移 <<
根据指定位数将原数各个二进制位左移指定位,高位左移溢出则舍弃,低位空位则用0补齐。
当该数左移时溢出舍弃的高位中不包含1时,左移类似乘法,左移n位相当于该数乘以2^n。
6.右移 >>
根据指定位数将原数各个二进制位右移指定位,移到右端的低位被舍弃。左端高位,如果无符号数,补0。对于有符号数,移入0称为逻辑右移,移入1的称为算术右移。
位运算实现加减乘除
1.位运算实现加法
无论哪种进制的加法,其核心都是“和”、“进位”。位运算有一个特点,首先是:
位运算的异或运算与“和”一致:
异或 1^1=0;1^0=1;0^0=0
求和 1+1=0;1+0=1;0+0=0
位运算的与运算与“进位”一致:
位与 1&1=1;1&0=0;0&0=0
进位 1+1=1;1+0=0;0+0=0
综合上述两个特点实现加法,代码如下:(分析详见注释)
int add(int a, int b) //递归形式
{
if(b==0) //递归结束条件:如果右加数为0,即不再有进位,结束运算
return a;
int s = a^b;
int c = (a&b)<<1; //进位左移1位
return add(s, c); //把'和'和'进位'相加
}
int add(int a, int b) //循环形式
{
int s, c;
while(b != 0)
{
s = a^b;
c = (a&b)<<1;
a = s;
b = c;
}
return a;
}
2.位运算实现减法
减法实质使用加法实现的。先把减数求负,然后做加法。对一个数求负的方法是:将该数连符号位一起取反,然后加一。
代码如下:(先求负,在相加)
int negtive(int i) //求负
{
return add(~i, 1);
}
int subtraction(int a, int b) //减法运算:利用求负操作和加法操作
{
return add(a, negtive(b));
}
3.位运算实现乘法
乘法需要考虑符号位,需要进行符号位的提取,以及负数的取正。乘法的实现也可借助加法,a*b可以看成b个a相加,通过循环实现,代码如下:(此法慢,其他方法不做赘述)
int getsign(int i){ //取一个数的符号,看是正还是负
return (i>>31);
}
int bepositive(int i){ //将一个数变为正数,如果本来就是正,则不变;如果是负,则变为相反数。注意对于-2147483648,求负会溢出。
if(i>>31)
return negtive(i);
else
return i;
}
int multiply(int a, int b){ //循环法实现加法
bool flag = true;
if(getsign(a) == getsign(b)) //积的符号判定
flag = false;
a = bepositive(a);//先把乘数和被乘数变为正数
b = bepositive(b);
int ans = 0;
while(b){
ans = add(ans, a);
b = subtraction(b, 1);
}
if(flag)
ans = negtive(ans);
return ans;
}
4.位运算实现除法
除法可借助减法实现,从被除数上减去除数,看能减多少次之后变得不够减。代码如下:(此法慢,其他方法不做赘述)
int division(int a, int b){
if(b==0)
return 0;
bool flag = true;
if(getsign(a) == getsign(b)) //积的符号判定
flag = false;
a = bepositive(a);
b = bepositive(b);
int n = 0;
a = subtraction(a, b);
while(a>=0){
n = add(n, 1);
a = subtraction(a, b);
}
if(flag)
n = negtive(n);
return n;
}
位运算常用技巧
1.计算二进制数中1的个数
通过与初始值为1的标志位进行与运算,判断最低位是否为1;然后将标志位左移,判断次低位是否为1;一直这样计算,直到将每一位都判断完毕。
int countOf1(int num){
int count = 0;
unsigned int flag = 1;
while(flag){
if(num & flag)
count++;
flag = flag << 1;
}
return count;
}
2.判断某数是否为2的n次方
一个数是2的n次方,则这个数的最高位是1,其余位为0。
bool is2Power(int num){
bool flag = true;
num = num & (num - 1); //计算num和num - 1的与的结果
if(num) //如果结果为0,则不是2的n次方
flag = false;
return flag;
}
3.判断某数的奇偶性
判断奇偶性,实质是判断最后一位是否是1。
// 判断一个数的奇偶性.返回1,为奇数;返回0,为偶数
bool isOdd(int num){
return num & 1 == 1;
}
4.交换两数
交换a和b:
a=a∧b;
b=b∧a;
a=a∧b;
5.求绝对值
将原数n右移31位,可以获得n的符号。
// 若n为正数,得到0;若n为负数,得到-1
int myAbs(int n){
return (n ^ n >> 31) - (n >> 31);
}
6.求平均值
int getAverage(int m,int n){
return (m + n) >> 1;
}