题目描述:不用加减乘除做加法_牛客网
写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。
解法(1):空间O(1),时间O(n)。
不能用四则运算,只能用二进制位运算模拟。二进制中,异或是不进位的加法,进位可以用相与并左移1得到,继续令二者相加直到进位为0。
举例,4+6=10。
①用‘与运算+右移1’模拟进位位置,用异或模拟不进位的相加结果。
②得到8和2。重复①,直至没有进位。
# 死循环版
死循环原因:当进位结果导致原有储存位数扩增时,异或结果为负数时位数扩增体现为数值位左边补1,则两者的进位将永远不为0,陷入死循环。陷入死循环的场景是 负数+正数,且正数的绝对值比负数大。
分析:因为正数的绝对值大,所以用多少位存储取决于正数,设数值位用n-1位存储,第n位是符号位,正数的数值位最高位即第n-1位是1。由于负数绝对值较小,因此补码的数值位值大于正数数值位,因此负数的数值位最高位即第n-1位也是1,两个加数相与后得到最高数值位即第n-1位是1的正数。两个加数异或后得到一个n位负数。相与结果左移一位得到进位结果,进位结果的数值位是n位,因此异或结果的负数也必须用n位数值位表示,则负数由n+1位表示,第n位和第n+1位符号位均为1。新的加数为该负数和左移结果。继续重复上面的操作,由于进位结果和负数的数值最高位都为1,相与后又会导致数位扩增,进位结果永远不为0,陷入死循环。举例如下图。
示例分析:-1+2得到的不进位和、进位数(两个加数)的二进制形式为11{0...}1、01{0...}0,其中{}内表示循环过程中增加的1或0的个数。可以看出,当进位只有最高数值位为1时,继续操作将不断左移最高位1,不进位和将不断增加数值位中的0,而正确和可以表示为{0...}1,因此只要取出{0...}1值,即去掉符号位、数值位最高位剩下的二进制值,亦即异或结果的低n-2位返回即可。
实现1:限制最高存储位数为32(因为测试用例不超过32位)。当进位1不断左移到达2^31时,继续左移将溢出,此时丢弃进位,返回异或结果的低30位数。
# -*- coding:utf-8 -*-
实现2:其实,当进位数的二进制位中只有一个1且1在最高的数值位上时,最终和的所有信息已经存储在不进位加和数当中,此时就可以丢弃进位了。该方法适用于事先不知道存储位数限制的情景。
总结:当两个加数一正一负,且正数的绝对值≥负数的绝对值,且正数的二进制位中只有一个1时,属于可能陷入死循环的特殊情况,提前结束,返回负数的n-2位二进制数。
# -*- coding:utf-8 -*-
其他解法
- 添加越界检查才能避免负数死循环。跟0xFFFFFFFF(边界数)相与,相当于将负数的二进制补码视为某个正数的原码,有(2^32)-原负数=该正数,即二者对于2^32同模。符号参与的进位1将一直往左移直到超过边界数时退出循环。最后要再将该“正数”转化为对应补码的负数。关于补码https://www.cnblogs.com/zhangziqiu/archive/2011/03/30/ComputerCode.html
- 正负数判断及还原:对于负数来说,~((n&0xFFFFFFFF)^0xFFFFFFFF)=n。
以-15为例,~(-15)=241^0xFF=((-15)&0xff)^0xff。
11110001^0xFF -----> 00001110(14),相当于00001111-1,即15-1。
~(00001110) -----> 11110001(补码) ,对应的十进制数是 -15
此处有一个规律:~n = -(n+1)。取反改变符号!!
总结:对于负数a,
- 令b=a&0xFFFFFFFF,相当于将a往前拨2**32步得到正数b,即b=a+2**32;
- 令c=b^0xFFFFFFFF,相当于求(2**8-1)减去b得到正数c,即c=(2**8-1)-b;
- 由上面二式可得,a=-c-1,即c比a的绝对值小1;
- 令d=~c,相当于将c往后拨2**8-1步得到负数d,由公式得d=~c=-(c+1)=a。
*设置成32位应该是考虑到其他语言的特点,测试样例中不会出现超过32位整型的数,实际上,把边界调大的话,不会影响最终结果。参考https://blog.csdn.net/lrs1353281004/article/details/87192205
# -*- coding:utf-8 -*-
。