LeetCode题目43:字符串相乘

简介: LeetCode题目43:字符串相乘

作者介绍:10年大厂数据\经营分析经验,现任大厂数据部门负责人。

会一些的技术:数据分析、算法、SQL、大数据相关、python

欢迎加入社区:码上找工作

作者专栏每日更新:

LeetCode解锁1000题: 打怪升级之旅

python数据分析可视化:企业实战案例

备注说明:方便大家阅读,统一使用python,带必要注释,公众号 数据分析螺丝钉 一起打怪升级

LeetCode题目43 “字符串相乘” 要求计算两个以字符串形式表示的非负整数的乘积,并返回结果的字符串形式。本文将详细介绍解决这一问题的第一种方法:模拟乘法手算过程。

题目描述

输入:两个字符串 num1num2,分别表示两个非负整数。

输出:一个字符串,表示 num1num2 的乘积。

输入输出格式

输入
  • num1num2:非负整数的字符串形式,不包含任何前导零,除非是数字0本身。
输出
  • 返回值:两个整数乘积的字符串形式。

示例

示例 1
输入: num1 = "2", num2 = "3"
输出: "6"
示例 2
输入: num1 = "123", num2 = "456"
输出: "56088"

方法一:模拟乘法手算

解题步骤

  1. 特殊情况处理:如果 num1num2 为 “0”,直接返回 “0”。
  2. 初始化结果数组:创建一个长度为 len(num1) + len(num2) 的数组 result 来存储乘积的每一位。
  3. 逐位相乘:遍历 num1num2 的每一位,将每一位的乘积累加到 result 的对应位置。
  4. 处理进位:从 result 的最后一位开始处理进位,将每位的数字处理为个位数,进位加到下一位。
  5. 生成结果字符串:将 result 数组转换成字符串,去除前导零。

详细规范带中文注释的代码示例

def multiply(num1: str, num2: str) -> str:
    # 特殊情况处理,任一数字为'0',结果即为'0'
    if num1 == "0" or num2 == "0":
        return "0"
    # 结果最多为两数字长度之和
    result = [0] * (len(num1) + len(num2))
    # 从后向前遍历两个字符串
    for i in range(len(num1)-1, -1, -1):
        for j in range(len(num2)-1, -1, -1):
            # 乘积在result对应的索引位置
            mul = (ord(num1[i]) - ord('0')) * (ord(num2[j]) - ord('0'))
            # 加上result[i+j+1]位置的旧值
            sum_ = mul + result[i+j+1]
            # 更新result数组
            result[i+j+1] = sum_ % 10  # 当前位
            result[i+j] += sum_ // 10  # 进位
    # 将结果数组转换为字符串,跳过前导零
    result_str = ''.join(map(str, result))
    return result_str.lstrip('0')  # 去除可能存在的前导零
# 示例调用
print(multiply("123", "456"))  # 输出: "56088"

算法分析

  • 时间复杂度:O(MN),其中 M 和 N 分别是 num1num2 的长度。每一位的乘积需要遍历一次,共有 MN 次操作。
  • 空间复杂度:O(M+N),用于存储乘积的数组。

方法二:基于四则运算优化的分治算法(Karatsuba算法)

当处理非常大的数字乘法时,传统的逐位相乘算法可能不够高效。Karatsuba算法提供了一个更快的递归方法,利用分治技术来加速大数的乘法过程。这种方法基于数位分割,将大数乘法问题转换为小数乘法问题,从而减少了乘法的总次数。

解题步骤

  1. 分割数字:将每个数分割成两部分,例如,123456可以分割为123和456,分割点基于数的长度。
  2. 递归计算
  • 设两个大数为 ( a ) 和 ( b ),分别分割为 ( a1, a0 ) 和 ( b1, b0 ),其中 ( a1, b1 ) 是高位部分,( a0, b0 ) 是低位部分。
  • 根据Karatsuba乘法,计算三个乘积:
  • ( p1 = a1 \times b1 )
  • ( p2 = a0 \times b0 )
  • ( p3 = (a1 + a0) \times (b1 + b0) )
  • 结果可以通过这三个乘积组合得到:( result = p1 \times 10^{2m} + (p3 - p1 - p2) \times 10^m + p2 ),其中 ( m ) 是分割的位数。
  1. 合并结果:根据上述公式合并计算结果,得到最终的乘积。

代码示例

def multiply(num1: str, num2: str) -> str:
    # 基础情况处理
    if len(num1) == 1 or len(num2) == 1:
        return str(int(num1) * int(num2))
    
    # 使得num1和num2长度相同,前面补0
    max_len = max(len(num1), len(num2))
    num1 = num1.zfill(max_len)
    num2 = num2.zfill(max_len)
    
    # 分割数字
    m = max_len // 2
    high1, low1 = num1[:-m], num1[-m:]
    high2, low2 = num2[:-m], num2[-m:]
    
    # 递归计算p1, p2, p3
    z0 = multiply(low1, low2)
    z1 = multiply(str(int(low1) + int(high1)), str(int(low2) + int(high2)))
    z2 = multiply(high1, high2)
    
    # 计算并合并结果
    return str(int(z2 + '0'*(2*m)) + (int(z1) - int(z2) - int(z0)) * (10**m) + int(z0))
# 示例调用
print(multiply("123", "456"))  # 输出: "56088"

算法分析

  • 时间复杂度:O(n1.585)。Karatsuba算法相比于传统O(n2)的方法有明显的时间复杂度优势,特别是在处理非常大的数字时。
  • 空间复杂度:O(n),主要消耗在递归过程中对数字的分割以及递归栈的使用上。

方法三:FFT(快速傅立叶变换)基础上的多项式乘法

快速傅立叶变换(FFT)是处理大整数乘法的一种高效算法,特别适用于数字非常大的情况。FFT可以将多项式乘法的时间复杂度从 (O(n^2)) 降低到 (O(n \log n)),使得它在多项式乘法和大整数乘法中非常有效。

解题步骤

  1. 表示为点值形式:将两个数表示为点值形式,这可以通过评估多项式在各点上的值实现。
  2. 使用FFT计算点值乘法:将两个输入数的多项式表示通过FFT转换到频域(点值形式),然后对应点值相乘。
  3. 逆FFT变换:将乘法后的点值结果通过逆FFT变换回系数表示形式,即得到原多项式乘法的结果。
  4. 处理结果和进位:将FFT的结果转换为数字,处理进位。

代码示例

这里提供一个基于FFT的多项式乘法的Python简化实现,考虑到FFT的复杂性,实际使用时可能需要依赖专门的库如numpyscipy

import numpy as np
def fft(a):
    n = len(a)
    if n == 1:
        return a
    omega_n = np.exp(2j * np.pi / n)
    omega = 1
    a0 = a[0::2]
    a1 = a[1::2]
    y0 = fft(a0)
    y1 = fft(a1)
    y = np.zeros(n, dtype=complex)
    for k in range(n // 2):
        y[k] = y0[k] + omega * y1[k]
        y[k + n // 2] = y0[k] - omega * y1[k]
        omega *= omega_n
    return y
def ifft(y):
    n = len(y)
    y_conj = np.conjugate(y)
    a = fft(y_conj)
    a = np.conjugate(a)
    return a / n
def multiply(num1, num2):
    # 初始化数据
    n = 1
    while n < max(len(num1), len(num2)):
        n *= 2
    n *= 2
    a = np.array([int(x) for x in num1][::-1] + [0] * (n - len(num1)), dtype=complex)
    b = np.array([int(x) for x in num2][::-1] + [0] * (n - len(num2)), dtype=complex)
    # FFT
    A = fft(a)
    B = fft(b)
    C = A * B
    # 逆FFT
    c = ifft(C).real.round().astype(int)
    # 处理进位
    carry = 0
    for i in range(len(c)):
        c[i] += carry
        carry = c[i] // 10
        c[i] %= 10
    # 去除前导0并转换为字符串
    while len(c) > 1 and c[-1] == 0:
        c = c[:-1]
    return ''.join(map(str, c[::-1]))
# 示例调用
print(multiply("123", "456"))  # 输出: "56088"

算法分析

  • 时间复杂度:O(n \log n),其中 n 是输入数字的位数。这是由FFT和逆FFT操作的复杂度决定的。
  • 空间复杂度:O(n),主要是存储FFT结果和临时数组的需要。

总结

下面的表格将展示它们在不同技术维度的性能和特点,包括时间复杂度、空间复杂度以及各自的优势和劣势。

特征 方法一:模拟乘法手算 方法二:Karatsuba算法 方法三:FFT乘法
时间复杂度 O(M*N) O(N^1.585) O(N log N)
空间复杂度 O(M+N) O(N) O(N)
优势 - 直观且易于实现
- 适合小数字乘法
- 快于传统方法
- 适合中等大小数字
- 非常高效,特别适合大数字乘法
- 大规模数字处理的首选
劣势 - 时间复杂度较高
- 不适合大规模数字
- 实现复杂度较高
- 需要处理数字分割
- 需要FFT库支持
- 处理进位相对复杂
适用场景 - 数字较小
- 教学或简单应用
- 数字大小适中
- 需要比直接方法快但实现复杂度可接受
- 处理非常大的数字
- 高性能计算需求


欢迎关注微信公众号 数据分析螺丝钉

相关文章
|
16小时前
|
C语言
详解Leetcode中关于malloc模拟开辟二维数组问题,涉及二维数组的题目所给函数中的各个参数的解读
详解Leetcode中关于malloc模拟开辟二维数组问题,涉及二维数组的题目所给函数中的各个参数的解读
6 1
|
11天前
|
算法 索引 Python
二刷力扣--字符串
二刷力扣--字符串
|
12天前
|
算法 容器
【LeetCode刷题】滑动窗口解决问题:水果成篮、找到字符串中所有字母异位词
【LeetCode刷题】滑动窗口解决问题:水果成篮、找到字符串中所有字母异位词
|
6天前
leetcode题解:28.找出字符串中第一个匹配项的下标
leetcode题解:28.找出字符串中第一个匹配项的下标
7 0
|
6天前
leetcode题解:1768.交替合并字符串
leetcode题解:1768.交替合并字符串
18 0
|
12天前
|
容器
【LeetCode刷题】栈和队列题目练习~
【LeetCode刷题】栈和队列题目练习~
【42页动态规划学习笔记分享】动态规划核心原理详解及27道LeetCode相关经典题目汇总(4)
【42页动态规划学习笔记分享】动态规划核心原理详解及27道LeetCode相关经典题目汇总
|
12天前
|
存储 算法
【42页动态规划学习笔记分享】动态规划核心原理详解及27道LeetCode相关经典题目汇总(1)
【42页动态规划学习笔记分享】动态规划核心原理详解及27道LeetCode相关经典题目汇总
|
11天前
|
算法 C++
【数据结构与算法】:关于时间复杂度与空间复杂度的计算(C/C++篇)——含Leetcode刷题-2
【数据结构与算法】:关于时间复杂度与空间复杂度的计算(C/C++篇)——含Leetcode刷题
|
11天前
|
算法 C++
【数据结构与算法】:关于时间复杂度与空间复杂度的计算(C/C++篇)——含Leetcode刷题-1
【数据结构与算法】:关于时间复杂度与空间复杂度的计算(C/C++篇)——含Leetcode刷题