描述
给出两个整数a
和b
,求它们的和
样例
如果a=1
并且b=2
,返回3
.
挑战
你可以直接使用return a+b
,但是不使用+
运算符
位运算知识扩展
&
按位与
二进制00000110
(6),00001011
(11) 按位与运算:
00000110
& 00001011
00000010
总结:同为1
则为1
,否则为0
|
按位或
按位或运算:
00000110
| 00001011
00001111
总结:只要有一个为1
则为1
,否则为0
^
按位异或
按位异或运算:
00000110
^ 00001011
00001101
总结:不相同则为1
,否则为0
~
按位取反
按位取反运算:
00000110
~
11111001
总结:0
为1
,1
为0
<<
左移
左边消失,右边补0
>>
右移
右边消失,左边补符号位
位运算加法
十进制中:
13 + 9
操作步骤:
- 不考虑进位,
sum = 13 + 9 = 12
; - 只考虑进位,
carry= 13 + 9 = 10
; - 如果
carry != 0
,使用sum
和carry
重复1,2步,直至carry = 0
为止,则结果为sum = 22
;
步骤:
1. sum = 13 + 9 = 12;
2. carry = 13 + 9 = 10;
3. carry != 0;
4. sum = 12 + 10 = 22;
5. carry = 12 + 10 = 0;
6. carry = 0, sum = 22;
二进制中:
13: 00001101 9: 00001001
1. sum = 00001101 + 00001001 = 00000100;
2. carry = 00001101 + 00001001 = 00010010;
3. carry != 0;
4. sum = 00000100 + 00010010 = 00010110;
5. carry = 00000100 + 00010010 = 00000000;
6. carry = 0, sum = 00010110 = 22;
第一步操作就是按位异或^
操作;
第二步操作是按位与&
操作,再左移<<
1
位.
所以,操作就可以使用递归或者迭代方式实现
递归代码:
int add(int num1, int num2) {
if (num2 == 0) {
return num1;
}
int sum = num1 ^ num2;
int carry = (num1 & num2) << 1;
return add(sum, carry);
}
迭代代码:
int add(int num1, int num2) {
int sum = num1 ^ num2;
int carry = (num1 & num2) << 1;
while (carry != 0) {
int a = sum;
int b = carry;
sum = a ^ b;
carry = (a & b) << 1;
}
return sum;
}
扩展减乘除
减法
13 - 9
可以转换为 13 + (-9)
,然后使用前面的加法方法实现减法
在计算机中,负数是以补码形式存在的:
-9 原码: 00001001
反码: 11110110
补码: 11110110 + 1 = 11110111
所以-9
在计算机中的表现形式是:11110111
.
~00001001 + 1
就可以得到-9
的表现形式.
总结:取反加一
所以:
int subtract(int num1, int num2) {
int sub = add(~num2, 1);
return add(num1, sub);
}
乘法
13 * 9
转换为加法就是9
个13
相加,符号最后判断,同号为正,异号为负.
所以:
int multiply(int a, int b) {
//取绝对值
int absa = a < 0 ? add(~a, 1) : a;
int absb = b < 0 ? add(~b, 1) : b; //如果为正 则直接取b,否则取反加一取补码
int sum = 0; //叠加值
int count = 0; //叠加次数
while (count < absb) { //叠加次数始终小于b
sum = add(sum, absa); //叠加依次
count = add(count, 1); //次数加一
}
//判断正负号, 按位异或,最高位为1则为负数,最高位为0则为正数
if ((a ^ b) < 0) {
sum = add(~sum, 1);
}
return sum;
}
上面的方法,如果乘数很大,即9
很大时,需要叠加的次数也就很多,运算起来就会很大,效率不高
可以参考十进制的乘法:
1 3
* 1 4
5 2
1 3
1 8 2
二进制:
1 1 0 1
* 1 1 1 0
0 0 0 0
1 1 0 1
1 1 0 1
1 1 0 1
1 0 1 1 0 1 1 0 (182)
相当于b从低位开始取值,如果为1,则加入sum中,为0,则不需要相加
取b的下一位, 即右移一位,取最低位, a右移一位,相当于在十进制中乘以10,重复上面.
直到最后b右移为0时,结束,判断符号
所以优化结果为:
int multiply(int a, int b) {
//取绝对值
int absa = a < 0 ? add(~a, 1) : a;
int absb = b < 0 ? add(~b, 1) : b;
int sum = 0;
while (absb > 0) {
if ((absb & 0x1) > 0) { //每次考察最后一位, 为 1 加, 为 0 跳过
sum = add(sum, absa);
}
absa = absa << 1; //a 左移一位, 在十进制中就是乘于10, 进一位,二进制中同理
absb = absb >> 1; //b 右移一位, 在十进制中就是除以10, 退一位, 二进制中同理
}
//判断符号
if ((a ^ b) < 0) {
sum = add(~sum, 1);
}
return sum;
}
除法
除法转换为减法, 被除数减除数,直到被除数小于除数位置,减的次数就是商,此时被除数就是余数.符号确认跟乘法一样,余数的符号和被除数一样.
int divide(int a, int b) {
int absa = a < 0 ? add(~a, 1) : a;
int absb = b < 0 ? add(~b, 1) : b;
int count = 0; //商
int rema = 0; //余数
while (absa >= absb) { //如果被除数小于除数,结束操作
count = add(count, 1); //次数加一
absa = subtract(absa, absb); //减
}
//判断符号
if ((a ^ b) < 0) {
count = add(~count, 1);
}
//判断余数符号
rema = a > 0 ? absa : add(~absa, 1);
return count;
}
跟乘法类似 a
很大 b
很小 减的次数就很大,所以可以增加步数去减
在计算机中,所有数都可以使用 [20,21,22,…,231] 表示,所以可以从31开始,如果可以减31倍,就把31倍的次数,依次类推
int divide(int a, int b) {
int absa = a < 0 ? add(~a, 1) : a;
int absb = b < 0 ? add(~b, 1) : b;
int sum = 0;
int rema = 0;
for (int i = 31; i >= 0; i--) {
//
if ((absa >> i) >= absb) {
sum = add(sum, 1 << i);
absa = subtract(absa, absb << i);
}
}
if ((a ^ b) < 0) {
sum = add(~sum, 1);
}
rema = a > 0 ? absa : add(~absa, 1);
}
结束