1. 程式人生 > >LeetCode43,一題讓你學會高精度演算法

LeetCode43,一題讓你學會高精度演算法

本文始發於個人公眾號:**TechFlow**,原創不易,求個關注

今天是LeetCode系列第22篇文章,今天講的內容是高精度演算法。

今天和大家討論的演算法是高精度,對應的LeetCode是第43題。題面其實沒什麼好說的,以字串的形式給定兩個數字,要求返回這兩個數字的乘積。之所以是以字串的形式給數字是因為這個數字可能會非常大,題目當中給定的範圍是110位的數字。對於Python來說這不是問題,但是對於C++和Java等語言來說這麼大的數字是無法以int型別儲存的,所以必須要使用字串來接收。

如果你使用Python,你可以不用任何演算法就AC這題,但是這沒有任何意義。那麼正確的方法應該怎麼做呢?

高精度與打豎式

這就需要我們的高精度演算法出場了,其實嚴格說起來高精度並不是一種演算法,而是一種思想。這個思想非常樸素,我敢保證我們每一個人都學過。還記得小學的時候,我們計算多位數的乘法是怎麼算的嗎?大家應該都不陌生才對,就是打豎式,like this:

我們人類要打豎式是因為我們只能計算一位數以內的加減乘除,超過一位的人腦不能直接計算,我們就需要用紙筆記錄下來進行計算。

紙筆計算的方法很簡單,就是一位一位地計算,用每一位數字依次去計算乘法,最後再移位相加起來就得到結果了。

比如在上圖的第一個例子當中,我們要計算15 * 16,我們先計算6 * 15的結果,再計算1 * 15,最後將兩個結果錯位相加,就得到了答案。我們要錯位的原因也很簡單,因為我們在計算15 * 1的時候,其實背後代表的是15 * 10。我們繼續拆分問題,當我們計算6和15相乘的時候,又是怎麼計算的呢?順著這個思路,整個過程可以進一步被劃分成先計算6和5相乘,再計算6和1相乘。

最後,我們把兩個較大數字的相乘拆分成了在每一位上的數字相乘。到了這裡,剩下的就簡單了,也就是說我們可以把這兩個很大的數字用兩個陣列來儲存,陣列當中的每一位儲存數字上的一位。

比如我們要計算123 * 224, 我們的第一個陣列是[1, 2, 3],我們的第二個陣列是[2, 2, 4]。我們仿照乘法豎式中的方法計算這兩個陣列當中兩兩的乘積,並將它們拼裝成答案。

      1 2 3
* 2 2 4
____________
4 9 2
2 4 6
2 4 6
____________
2 7 5 5 2

同樣我們用陣列來儲存中間和最後的結果,最後的結果就是:[2, 7, 5, 5, 2]。由於題目需要我們要返回的是字串,所以我們還需要將數組裡的內容再拼接成字串。

這種用陣列來模擬數字進行加減乘除運算的方法就叫做高精度演算法,相信大家也都看到了,嚴格說起來這並不是一個演算法,而只是一種思想。今天的題目出的是乘法,我們利用同樣的方法也可以計算加減和除法。其中加減法非常簡單,而除法則要複雜得多,也是高精度當中最難實現的部分。這裡我們不做過多的拓展,計算的方法同樣是打豎式,感興趣的同學可以自行實現。

進位和前導零

當我們理清楚了打豎式的方法之後,我們還要面臨進位和前導零的問題。

進位應該很容易理解,我們需要在計算乘法的時候判斷當前位置的元素是否大於等於10,如果超過10的話,我們則需要進行進位。我們只需要將它除以10,得到的結果就是我們需要進位的值。除此之外就是前導零的問題,我們都知道除了零以外的合法數字是不允許首位出現0的,但是由於我們計算的是乘法,所以當其中某一個數為0會得到整體的結果為0,但是表示在陣列當中則是多個0.

舉個簡單的例子,比如123 * 0,最後得到的應該是0,但是由於我們用陣列表示了乘法運算當中的每一位,並且還進行了加法計算,所以會導致出現000的結果。這種情況我們要做特殊的處理,不過這也不復雜。最後我們把上面所有的思路都整理一下,就可以得到結果了。

我們來看下程式碼:

class Solution:

def multiply(self, num1: str, num2: str) -> str:
# 將字串轉化成陣列
# 翻轉陣列,因為我們用第0位表示個位
arr1 = [ord(i) - ord('0') for i in num1][:: -1]
arr2 = [ord(i) - ord('0') for i in num2][:: -1]

# 建立結果陣列,可以證明結果的長度最多是n + m
n, m = len(arr1), len(arr2)
ret = [0 for i in range(n + m + 1)]

for i in range(n):
for j in range(m):
# 按位相乘,計算進位
ret[i + j] += arr1[i] * arr2[j]
if ret[i+j] >= 10:
ret[i+j+1] += ret[i+j] // 10
ret[i+j] %= 10

# 最後把陣列再轉化成字串返回
# 去除前導零
result = ''.join(map(str, ret))[::-1].lstrip('0')
return result if len(result) > 0 else '0'

今天的題只是Medium難度,並不算困難,會選這題的原因主要是為了高精度演算法。高精度演算法本身並不難,也並不常用即使是在演算法比賽當中也不常見。但是它給了我們一個思路,當我們要計算的數值超過計算機目前承載能力的時候,我們還有什麼方法?

當然這題我們也可以取巧,因為Python當中內建了大整數,當它檢測到我們的計算結果超過範圍的時候,會自動轉化成大整數來進行計算。所以這題如果我們使用Python,可以只用幾行程式碼搞定:

class Solution:
def multiply(self, num1: str, num2: str) -> str:
num1 = int(num1)
num2 = int(num2)
return str(num1 * num2)

今天關於高精度演算法的內容就到這裡,如果覺得有所收穫,請順手點個關注或者轉發吧,你們的舉手之勞對我來說很重要。

![](https://user-gold-cdn.xitu.io/2020/3/22/170ffe9e97c6eaf5?w=258&h=258&f=png&