这种面试题...能想到的就是用位运算代替
在讲解之前,首先普及一点知识
与运算(全一才是一):
0 & 0 = 0
1 & 0 = 0
0 & 1 = 0
1 & 1 = 1
或运算(有一就是一):
0 | 0 = 0
1 | 0 = 1
0 | 1 = 1
1 | 1 = 1
非运算(就是唱反调):
~1 = 0
~0 = 1
异或运算(不同才是一):
0 | 0 = 0
1 | 0 = 1
0 | 1 = 1
1 | 1 = 0
移位运算:
左移末尾自动补0
000010 << 1 = 000100
右移,含符号,首部补符号位
1110110 >> 1 = 11110111
右移,无符号,首部自动补零
1110110 >>> 1 = 01111011
好了,普及就到这了,接着往下看吧
-----------------------可爱的分割线---------------------------------
加法:
因为位运算都是针对二进制的,我们不太好理解,那我们看一下十进制的时候咱是怎么算的:
12+19 = 31
首先,从末尾开始相加,加完后溢出的部分放到前面与下一位相加,这是幼儿园的做法;
那我们换个思路,首先不考虑进位,首先逐位相加,得到的结果与进位结果相加,不断迭代直到没有进位;
那上面那个题
12+19,逐位相加,
得到21,进位10,逐位相加,
31,进位0,得到结果。
其实二进制也是一样的,加法问题就转化成了:
1.获取逐位相加的结果
2.获取进位的值
不断将这两个结果在此相加,直到没有进位,那不就是最终的结果了嘛(笑)
单个位的加法规则:
0 + 0 = 0 (进位 0)
1 + 0 = 1 (进位 0)
0 + 1 = 1 (进位 0)
1 + 1 = 0 (进位 1)
发现没,逐位加法和异或操作结果一样,获取进位和与操作后左移一位结果一样;
解决了,代码如下:
public int add(int a, int b){
while(b != 0){
int sum = a ^ b;
int pos = (a & b) << 1;
a = sum;
b = pos;
}
return a;
}
-----------------------可爱的分割线---------------------------------
减法:
emmmmmmmmmmmm,起初我还想了很久,后来真是被自己蠢到了
a - b 不就是 a + (-b) 么......
那问题就变成了怎么用位运算取到 -b
这个..就很简单了 -n = ~n+1
代码如下:
public int minus(int a,int b) {
return add(a, ~b+1) ;
}
对了,我们不是不让用加减乘除符号嘛,好好好,我改...
public int minus(int a,int b) {
return add(a, add(~b, 1));
}
-----------------------可爱的分割线---------------------------------
乘法:
符号问题的话,不考虑,全按整数算,大不了最后取反就是了。
最简单的,乘法不就是多个加法嘛,
对,引用我高中数学老师的话,我不能说你错,但你这也不对
因为太--------慢--------了--------
我们的算法最差也会在32次循环内结束,如果累加的话,就会导致计算规模随b绝对值的增大而增大,这不是我们希望看到的。
还是那个思路,正常十进制是怎么算的
12*56,首先个位相乘,十位相乘后乘十,百位乘十后乘百....最后结果相加
沿着这个思路,到了二进制反而简单了,为什么呢
首先,乘数只有0和1啊,即全清空和全保留,好像.. 啥也不用做诶
乘十,到了这里就变成了乘2,那....左移就可以了
至于结果累加.....我们上面不是做过了嘛~
看代码吧:
public int multi(int a, int b){
int ans = 0;
int index = 0;
while(b != 0){
if( (b&1) == 1){// 判断最后一位是不是1
ans = add(ans, a << index);
}
index = add(index, 1);
b = b >> 1;
}
return ans;
}
-----------------------可爱的分割线---------------------------------
emmmmmmmmmmmm,我早就想吐槽楼上了,一点都不可爱好吗!
呼,终于到最后一个了,还是最简单的思路,反复的减去除数直至被除数小于除数就行了
但又回到了问题规模的问题,如果被除数过大,除数过小,就会出现循环次数过多的问题
这次我们就用乘法的逆运算,先找结果中第一个一的位置,即满足被除数>=除数<<x中x的最大值
然后相减,即贪心算法
代码如下:
public int divide(int a, int b) {
int flag = 1;
// 处理符号问题
if(a >> 31 == 1){
a = add(~a, 1);
flg = add(~flg, 1);
}
if(b >> 31 == 1){
b = add(~b, 1);
flg = add(~flg, 1);
}
int re = 0;
while (a >= b){
int aa = 1, temp = b;
while (temp < (a>>1)){
temp <<= 1;
aa <<= 1;
}
a -= temp;
re += aa;
}
return re*flag;
}
-----------------------可爱的分割线---------------------------------
(我丢~楼上砍你哦!)
总结,
上面的操作有一些位运算的技巧在这里说一下:
b & 1 :取一个数的最后一位
~n+1 :对n取反
-n &n :整数二进制串中最后一个1
n&(n-1) :去掉整数二进制串中最后一个1
n ^ n = 0
其实,这篇文章还是不太专业的,比如,举例时用的都是整数,细心的同学可能发现我没有加判定条件,那负数为什么也适用呢?这就涉及到计算机编码的知识了,其实就是补码和反码,有兴趣的同学合一自行百度;
还有就是没有考虑到整数溢出的问题,int型变量的取值范围是[-2^31, 2^31-1],对于
(2^31-1)+1这样的操作是会造成溢出的,但如何处理不在我们今天的讨论范围,有兴趣的同学可以自行百度
好啦,就这么多吧,这里只是一个位运算的引子,其实计算机还是蛮有趣的,共勉。