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 <N10\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)

实现结果


实现结果 | 竖式运算
实现结果 | 竖式运算(优化)
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,444评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,421评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,363评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,460评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,502评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,511评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,280评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,736评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,014评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,190评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,848评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,531评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,159评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,411评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,067评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,078评论 2 352