LeetCode 43. 字串相乘 | Python
43. 字串相乘
題目來源:力扣(LeetCode)https://leetcode-cn.com/problems/multiply-strings
題目
給定兩個以字串形式表示的非負整數 num1 和 num2,返回 num1 和 num2 的乘積,它們的乘積也表示為字串形式。
示例 1:
輸入: num1 = "2", num2 = "3"
輸出: "6"
示例 2:
輸入: num1 = "123", num2 = "456"
輸出: "56088"
說明:
- num1 和 num2 的長度小於110。
- num1 和 num2 只包含數字 0-9。
- num1 和 num2 均不以零開頭,除非是數字 0 本身。
- 不能使用任何標準庫的大數型別(比如 BigInteger)或直接將輸入轉換為整數來處理。
解題思路
思路:豎式運算
首先審題,題目要求的是乘積,那麼我們可以模擬豎式乘法來計算乘積。
這裡先要注意一種情況,當 num1 和 num2 任意一個為 0 時,直接返回 0。
如果 num1 和 num2 都不為 0,那麼我們可以遍歷 num2 每一位和 num1 進行相乘,然後將每次相乘的結果進行累加。(這其實就是我們豎式乘法運算的思想)如下圖示:
之前我們曾遇到過 415. 字串相加,我們在後續的累加的部分可以直接使用此題的思想。還有一個需要注意的,除了 num2 最低位與 num1 的運算除外,其他位與 num1 的乘積都應該補 0。
具體程式碼實現見【程式碼實現 # 豎式運算】
在上面的演算法中,需要對每步計算計算的字串進行相加的過程,多次對字串進行操作,會消耗效能。
在這裡,我們對豎式運算進行優化,我們採用陣列代替字串儲存結果,避免頻繁操作字串。
先看題目後面的說明中,第 2、3 條說明 num1 和 num2 不存在前導 0 的情況,並且 num1 和 num2 中每位數只包含 0 到 9 之間的數字。在這裡,我們根據這個來確定定義陣列的長度。
我們知道,(自然數中)只有 1 位的數字最小的是 0,最大為 9。兩位數最小的是 10,最大的是 99。我們可以發現,這裡是有規律的。假設 n 為數字位數,那麼當 n = 1 時,最小數字為 $0=10^{0}$,最大數字為 $9 = 10^{1}-1$;當 n = 2 時,最小數字為 $10 = 10^{2-1}$,最大數字為 $99 = 10^{2}-1$。也就說,當 n 取大於 0 的數(正整數)時,n 位數最小數為 $10^{n-1}$,最大數為 $10^{n} - 1$。
現在我們假設,N1、N2 分別為 num1 和 num2 的長度。這裡看 num1 和 num2 分別去最小值和最大值時可能的長度。
- 當 $\rm{num1}$ 和 $\rm{num2}$ 取最小值時,也就是 $\rm{num1}= 10^{N1-1}, \rm{num2}= 10^{N2-1}$,那麼兩者的乘積 $\rm{num1} \times \rm{num2}= 10^{N1+N2-2}$,也就說此時乘積的長度為 $N1+N2-1$;
- 當 $\rm{num1}$ 和 $\rm{num2}$ 取最大值時,也就是 $\rm{num1}= 10^{N1}-1, \rm{num2}= 10^{N2}-1$,那麼兩者的乘積 $\rm{num1} \times \rm{num2}= 10^{N1+N2} - 10{N1}-10{N2}+1$,這裡我們可以發現,兩者的乘積是介於 [$10^{N1+N2-1}$, $10^{N1+N2}$] 之間的。也就說長度為 $N1+N2$
在這裡,兩數相乘的最大長度為兩數長度之和。那麼定義陣列 ans 長度為 len(num1)+len(num2)
。對於任意 $0\leq i <N1$ 和 $0\leq j < N2$,索引對應的數值乘積結果(最大兩位數,形如 'ab','0a' 的形式),結果第一位位於 ans[i+j] 中,第二位位於 ans[i+j+1] 中。可結合下圖理解:
具體程式碼見【程式碼實現 # 豎式運算(優化)】
程式碼實現
# 豎式運算
class Solution:
def multiply(self, num1: str, num2: str) -> str:
if num1 == '0' or num2 == '0':
return '0'
ans = '0'
N1, N2 = len(num1), len(num2)
# 從後往前遍歷 num2,與 num1 相乘
for i in range(N2-1, -1, -1):
carry = 0
k = int(num2[i])
# 補充 0
curr = ['0'] * (N2-i-1)
for j in range(N1-1, -1, -1):
product = int(num1[j]) * k + carry
carry = product // 10
curr.append(str(product%10))
# 確認 carry 是否不為 0,不為 0 則補充在最高位
if carry != 0:
curr.append(str(carry))
# 形成字串,放入下列求和方法中
curr = ''.join(curr[::-1])
ans = self.addStrings(ans, curr)
return ans
def addStrings(self, num1: str, num2: str) -> str:
# 用以儲存計算結果
ans = []
# 定義指標分別指向 num1, num2 末尾
p = len(num1) - 1
q = len(num2) - 1
# 儲存進位
carry = 0
# 模擬加法運算
# 這裡將 carry 放到條件中,是考慮後續計算結束後,還有進位,也就是 carry 為 1 的情況
while p >= 0 or q >= 0 or carry:
# 由於有可能出現索引溢位的現象,
# 當較短的字串索引溢位時,要在頭部新增 0,用以後續計算
elem1 = int(num1[p]) if p >= 0 else 0
elem2 = int(num2[q]) if q >= 0 else 0
# 模擬計算,注意加上進位
tmp = elem1 + elem2 + carry
# 相加結果可能大於 10
# 計算進位,並且就將結果模 10,餘數新增到 ans 頭部
carry = tmp // 10
# ans = str(tmp % 10) + ans
ans.append(str(tmp % 10))
# 往前繼續計算
p -= 1
q -= 1
return ''.join(ans[::-1])
# 豎式運算(優化)
class Solution:
def multiply(self, num1: str, num2: str) -> str:
if num1 == "0" or num2 == "0":
return "0"
N1, N2 = len(num1), len(num2)
ans = [0] * (N1+N2)
for i in range(N1-1, -1, -1):
a = int(num1[i])
for j in range(N2-1, -1, -1):
b = int(num2[j])
tmp = ans[i+j+1] + a * b
ans[i+j] += tmp // 10
ans[i+j+1] = tmp % 10
# 文章分析了取值為最小最大時,兩數乘積的長度為 N1 + N2 - 1 和 N1 + N2
# 這裡注意陣列中首元素
ans = ans[1:] if ans[0] == 0 else ans
return ''.join(str(x) for x in ans)
實現結果
歡迎關注
公眾號 【書所集錄】