shell 做加法運算_劍指面試題[65]——不用加減乘除做加法(位運算)
技術標籤:shell 做加法運算
題目描述:不用加減乘除做加法_牛客網
寫一個函式,求兩個整數之和,要求在函式體內不得使用+、-、*、/四則運算子號。
解法(1):空間O(1),時間O(n)。
不能用四則運算,只能用二進位制位運算模擬。二進位制中,異或是不進位的加法,進位可以用相與並左移1得到,繼續令二者相加直到進位為0。
舉例,4+6=10。
①用‘與運算+右移1’模擬進位位置,用異或模擬不進位的相加結果。
②得到8和2。重複①,直至沒有進位。
![ccd0f6524febfa90d055e4fdfb96916e.png](https://img.796t.com/res/2021/01-16/03/c24958fe64c949a7634eb0bb228af836.png)
# 死迴圈版 # -*- coding:utf-8 -*- class Solution: def Add(self, s, carry): # write code here while carry: s, carry = s^carry, (carry & s)<<1 return s
死迴圈原因:當進位結果導致原有儲存位數擴增時,異或結果為負數時位數擴增體現為數值位左邊補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位返回即可。
![f1b189c360a52dded582ebdde828b6a3.png](https://img.796t.com/res/2021/01-16/03/d3bf67ff45435579b1364dcb0ec6a868.png)
實現1:限制最高儲存位數為32(因為測試用例不超過32位)。當進位1不斷左移到達2^31時,繼續左移將溢位,此時丟棄進位,返回異或結果的低30位數。
# -*- coding:utf-8 -*- class Solution: def Add(self, num1, num2): # write code here if num1&num2: # 此時num1跟num2用32位儲存,數值位佔31位,最高數值位為1,如果相與結果左移將溢位 if num1&num2 == 0x3fffffff+1: # 返回低30位的結果 return (num1^num2)&0x3fffffff return self.Add(num1^num2, (num1&num2)<<1) return num1^num2
實現2:其實,當進位數的二進位制位中只有一個1且1在最高的數值位上時,最終和的所有資訊已經儲存在不進位加和數當中,此時就可以丟棄進位了。該方法適用於事先不知道儲存位數限制的情景。
總結:當兩個加數一正一負,且正數的絕對值≥負數的絕對值,且正數的二進位制位中只有一個1時,屬於可能陷入死迴圈的特殊情況,提前結束,返回負數的n-2位二進位制數。
# -*- coding:utf-8 -*-
class Solution:
def Add(self, num1, num2):
# write code here
tmp_carry = num1&num2
tmp_sum = num1^num2
if not tmp_carry:
return tmp_sum
if tmp_carry>0 and tmp_sum<0 and tmp_carry<<1 >= abs(tmp_sum)
and not tmp_carry&(tmp_carry-1):
return tmp_sum & (tmp_carry-1)
return self.Add(tmp_sum, tmp_carry<<1)
其他解法
- 新增越界檢查才能避免負數死迴圈。跟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。
![a60b4dc0fc9671062930f3edcb0a6524.png](https://img.796t.com/res/2021/01-16/03/be0ce3a9b4d7e5ec52bef58df7e88ed4.png)
11110001^0xFF -----> 00001110(14),相當於00001111-1,即15-1。
~(00001110) -----> 11110001(補碼) ,對應的十進位制數是 -15
![e5c25440cacf8b1efed581d15d8d2739.png](https://img.796t.com/res/2021/01-16/03/5f48666d91fde9e3e1bd9770a99964cd.png)
此處有一個規律:~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 -*-
class Solution:
def Add(self, s, carry):
while(carry):
s, carry = (s^carry) & 0xFFFFFFFF,((s & carry)<<1) & 0xFFFFFFFF
# [-2^31,(2^31)-1]
return s if s<=0x7FFFFFFF else ~(s^0xFFFFFFFF)
。