#43 数学模拟 中等

字符串相乘

在 LeetCode 上查看 ↗

🎧 语音讲解

开车或通勤时可听,跟着思路走一遍

速度

📋 题目描述

给定两个以字符串形式表示的非负整数 num1num2,返回 num1num2 的乘积,它们的乘积也表示为字符串形式。

注意:不能使用任何内置的 BigInteger 库或直接将输入转换为整数。

示例 1

输入:num1 = "2", num2 = "3"
输出:"6"

示例 2

输入:num1 = "123", num2 = "456"
输出:"56088"
123 × 456 = 56088,模拟竖式乘法:每位相乘后按位累加进位。

💭 模拟答题者思考

1. 我先想暴力:把两个字符串转成 int 再相乘——题面明确禁止,且长度可达 200 位,会溢出。

2. 重复在哪里?竖式乘法里,每一位都要与另一个数的每一位相乘,再把部分积按位对齐相加。这个「对齐 + 进位」过程可以抽象成数组操作。

3. 关键观察:num1[i] × num2[j] 的结果(最多两位)应写入结果数组的 res[i+j+1](个位)和 res[i+j](十位进位)。开一个长度 m+n 的数组足够存放最终乘积。

4. 例 "123" × "456"3×6=18 写入 res[4..5]3×5=152×6=12 等同理累加并进位。全部数位对处理完后,从左到右跳过前导零,拼接成字符串。

5. 特判:任一串为 "0" 直接返回 "0";结果全零时也要返回 "0" 而非空串。

🧠 变量语义(先读这三句再编码)

变量类型语义(三句法)
m, nint定义num1num2 的长度
维护:乘积最多 m+n 位,结果数组长度由此确定
更新:初始化后不变
resint[]定义:长度为 m+n 的数位数组,res[k] 表示乘积从右数第 k 位的数字(低位在右)
维护:模拟竖式乘法,num1[i]×num2[j] 的贡献落在 res[i+j]res[i+j+1]
更新:每对数位相乘后 res[p2] += mul,再向 res[p1] 传递进位
i, jint定义num1num2 当前参与相乘的字符下标(从右向左)
维护:双重循环枚举所有数位对,覆盖竖式中每一次「个位×个位、个位×十位…」
更新im-10,内层 jn-10
mulint定义:当前两位数字的乘积 int(num1[i]) × int(num2[j])
维护:范围 0..81,加上已有低位后可能产生进位
更新:每对 (i,j) 重新计算
p1, p2int定义mulres 中对应的十位、个位下标,p2 = i+j+1p1 = i+j
维护:下标 i 越靠左(高位)、j 越靠左,乘积贡献越靠高位
更新:随当前 (i,j) 变化

⌨️ 落码步骤

1. 若 num1 == "0" or num2 == "0",返回 "0"

2. 令 m, n = len(num1), len(num2),初始化 res = [0] * (m + n)

3. 双重循环:im-10jn-10

4. 计算 mul = int(num1[i]) * int(num2[j])p1 = i+jp2 = i+j+1

5. sum = mul + res[p2]res[p2] = sum % 10res[p1] += sum // 10(个位落位、进位向高位传递)

6. 从左到右跳过前导零,将 res 剩余数位拼接为字符串返回

💻 代码实现

class Solution:
    def multiply(self, num1: str, num2: str) -> str:
        if num1 == "0" or num2 == "0":
            return "0"
        m, n = len(num1), len(num2)
        res = [0] * (m + n)
        for i in range(m - 1, -1, -1):
            for j in range(n - 1, -1, -1):
                mul = int(num1[i]) * int(num2[j])
                p1, p2 = i + j, i + j + 1
                total = mul + res[p2]
                res[p2] = total % 10
                res[p1] += total // 10
        # 跳过前导零
        start = 0
        while start < len(res) - 1 and res[start] == 0:
            start += 1
        return "".join(str(d) for d in res[start:])
class Solution {
public:
    string multiply(string num1, string num2) {
        if (num1 == "0" || num2 == "0") return "0";
        int m = num1.size(), n = num2.size();
        vector<int> res(m + n, 0);
        for (int i = m - 1; i >= 0; i--) {
            for (int j = n - 1; j >= 0; j--) {
                int mul = (num1[i] - '0') * (num2[j] - '0');
                int p1 = i + j, p2 = i + j + 1;
                int total = mul + res[p2];
                res[p2] = total % 10;
                res[p1] += total / 10;
            }
        }
        int start = 0;
        while (start < (int)res.size() - 1 && res[start] == 0) start++;
        string ans;
        for (int k = start; k < (int)res.size(); k++)
            ans += char('0' + res[k]);
        return ans;
    }
};
// 时间 O(m×n),空间 O(m+n)

📈 复杂度分析

时间复杂度 O(m × n)(m、n 为两串长度,每位与每位相乘一次)
空间复杂度 O(m + n)(结果数组长度最多 m+n 位)

⚠️ 常见坑

下标写反:num1[i]×num2[j] 的个位在 res[i+j+1]、十位进位在 res[i+j],不是 res[i+j] 存个位。

忘记累加已有值:应写 total = mul + res[p2],该位可能已被之前的数位对贡献过。

前导零处理不当:结果数组首位常为 0,需跳过;但若乘积为 0 必须返回 "0" 而非空字符串。

🔍 必测边界 Case

Case 1:乘数为 0
num1 = "0", num2 = "12345" → "0"(任一侧为 0 即返回 "0")
Case 2:单 digit
num1 = "2", num2 = "3" → "6"(最小非平凡输入)
Case 3:含前导零的乘积位
num1 = "99", num2 = "99" → "9801"(中间结果数组有前导零,输出需跳过)
Case 4:长度差大
num1 = "1", num2 = "99999999999999999999" → "99999999999999999999"(一位数乘大数)
Case 5:经典样例
num1 = "123", num2 = "456" → "56088"