题目描述

✅ 43. 字符串相乘

image-20260928190132029

题意分析

两个非负整数分别用十进制字符串 num1、num2 表示,要求返回它们的乘积字符串。不能把整个输入转换成整数,也不能借助大整数库;把单个数字字符转换成 0 到 9 后计算是允许的。

两个字符串都不含多余的前导零,零只会表示为 "0"。结果也应使用正常的十进制表示:非零结果不保留前导零,零返回 "0"。输入最多各有 200 位,因此需要直接在数位上完成乘法。

解法:数组模拟竖式乘法

核心思路

[!blue]

竖式乘法把两个数拆成数位:每一对数字相乘,再按它们的位权累加。设两串长度为 $m$、$n$,两数分别小于 $10^m$ 和 $10^n$,乘积一定小于 $10^{m+n}$,所以用长度为 m + n 的数组 digits 就能容纳结果;数组右端保存个位。

num1[i] 距离个位有 m - 1 - i 位,num2[j] 距离个位有 n - 1 - j 位。两者相乘后,乘积的本位距离个位有 m + n - 2 - i - j 位,换成结果数组从左到右的下标就是 pos = i + j + 1。因此,本位写到 digits[pos],进位写到它左边的 digits[pos - 1]。

同一个结果位置会收到多对数字的贡献,不能直接写入本次乘积。应先计算 sum = digits[pos] + x * y,把 sum % 10 留在本位,再把 sum / 10 累加到高一位。这个拆分只改变存储形式,不改变数组表示的总数值。

两层循环都从右向左进行。高位暂时累加到大于 9 也没关系:之后轮到该位置作为本位计算时,会把旧值一起拆成数字和进位。这样的顺序保证每个位置最后一次作为本位处理时,来自右侧的进位已经到齐;最左侧只接收最终进位,而乘积最多 m + n 位,不会再溢出数组。

所有数位对处理完后,数组就表示完整乘积。任一输入为 "0" 时提前返回;其余情况跳过数组前导零,把剩余数字依次拼成字符串。

解题步骤

  1. 任一输入为 "0" 时直接返回 "0",否则创建长度为 m + n 的零数组 digits。
  2. 从两串末尾向前枚举下标 i、j,把当前字符转换为数字 x、y。
  3. 令 pos = i + j + 1,计算 sum = digits[pos] + x * y。
  4. 更新 digits[pos] = sum % 10,并执行 digits[pos - 1] += sum / 10。
  5. 完成所有乘积累加后,找到第一个非零位置,将其及后面的数字拼接并返回。

代码实现

class Solution {
    public String multiply(String num1, String num2) {
        if ("0".equals(num1) || "0".equals(num2)) {
            return "0";
        }

        int m = num1.length();
        int n = num2.length();
        int[] digits = new int[m + n];

        for (int i = m - 1; i >= 0; i--) {
            int x = num1.charAt(i) - '0';

            for (int j = n - 1; j >= 0; j--) {
                int y = num2.charAt(j) - '0';
                int pos = i + j + 1;
                // 这个位置可能已有其他数位贡献,先合并旧值再处理进位。
                int sum = digits[pos] + x * y;

                digits[pos] = sum % 10;
                // 高位贡献要累加,不能覆盖此前的进位。
                digits[pos - 1] += sum / 10;
            }
        }

        StringBuilder ans = new StringBuilder();
        int idx = 0;

        while (idx < digits.length && digits[idx] == 0) {
            idx++;
        }

        while (idx < digits.length) {
            ans.append(digits[idx]);
            idx++;
        }

        return ans.toString();
    }
}
func multiply(num1 string, num2 string) string {
    if num1 == "0" || num2 == "0" {
        return "0"
    }

    m := len(num1)
    n := len(num2)
    digits := make([]int, m+n)

    for i := m - 1; i >= 0; i-- {
        x := int(num1[i] - '0')
        for j := n - 1; j >= 0; j-- {
            y := int(num2[j] - '0')
            pos := i + j + 1
            // 这个位置可能已有其他数位贡献,先合并旧值再处理进位。
            sum := digits[pos] + x*y

            digits[pos] = sum % 10
            // 高位贡献要累加,不能覆盖此前的进位。
            digits[pos-1] += sum / 10
        }
    }

    idx := 0
    for idx < len(digits) && digits[idx] == 0 {
        idx++
    }
    ans := make([]byte, 0, len(digits)-idx)
    for idx < len(digits) {
        ans = append(ans, byte(digits[idx])+'0')
        idx++
    }

    return string(ans)
}

复杂度分析

设两个输入字符串的长度分别为 $m$、$n$。

  • 时间复杂度:$O(mn)$。两层循环计算全部 $mn$ 对数位乘积,最后拼接结果需要 $O(m+n)$ 时间。
  • 辅助空间复杂度:$O(m+n)$,用于保存结果数位;构造返回字符串所需的空间也是 $O(m+n)$。

关键点总结

[!green]

  • 先根据位权确定落点:本位是 i + j + 1,进位是前一位。
  • 每次乘积都要和该位置已有贡献相加,再拆分本位与进位。
  • 从低位向高位处理,保证暂存在高位的进位最终也会被整理成一位数字。

易错点总结

[!yellow]

  • 数组需要 m + n 个位置;少开一位无法容纳完整乘积,最低位的下标也会越界。
  • 计算 sum 时漏掉 digits[pos],会丢失之前数位对产生的贡献。
  • 高位必须使用 += 累加;直接赋值会覆盖已有的进位或乘积贡献。
  • 本实现依赖两层循环都从右向左进行,不能在保持当前进位逻辑的同时随意改成正序。
  • 去除前导零时不能把零结果变成空字符串;代码先单独处理零输入,再拼接非零乘积。

相似题目

题目 难度 关联与区别
415. 字符串相加 简单 逐位加法是合并部分乘积的基础,本题还需处理不同乘积对应的位权偏移。
67. 二进制求和 简单 同样维护进位,但原题为二进制加法,本题是十进制逐位乘积累加。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2026/15837549
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!