作者介绍:10年大厂数据\经营分析经验,现任大厂数据部门负责人。
会一些的技术:数据分析、算法、SQL、大数据相关、python
欢迎加入社区:码上找工作
作者专栏每日更新:
备注说明:方便大家阅读,统一使用python,带必要注释,公众号 数据分析螺丝钉 一起打怪升级
LeetCode题目43 “字符串相乘” 要求计算两个以字符串形式表示的非负整数的乘积,并返回结果的字符串形式。本文将详细介绍解决这一问题的第一种方法:模拟乘法手算过程。
题目描述
输入:两个字符串 num1
和 num2
,分别表示两个非负整数。
输出:一个字符串,表示 num1
和 num2
的乘积。
输入输出格式
输入
- num1,num2:非负整数的字符串形式,不包含任何前导零,除非是数字0本身。
输出
- 返回值:两个整数乘积的字符串形式。
示例
示例 1
输入: num1 = "2", num2 = "3" 输出: "6"
示例 2
输入: num1 = "123", num2 = "456" 输出: "56088"
方法一:模拟乘法手算
解题步骤
- 特殊情况处理:如果
num1
或num2
为 “0”,直接返回 “0”。 - 初始化结果数组:创建一个长度为
len(num1) + len(num2)
的数组result
来存储乘积的每一位。 - 逐位相乘:遍历
num1
和num2
的每一位,将每一位的乘积累加到result
的对应位置。 - 处理进位:从
result
的最后一位开始处理进位,将每位的数字处理为个位数,进位加到下一位。 - 生成结果字符串:将
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 分别是
num1
和num2
的长度。每一位的乘积需要遍历一次,共有 MN 次操作。 - 空间复杂度:O(M+N),用于存储乘积的数组。
方法二:基于四则运算优化的分治算法(Karatsuba算法)
当处理非常大的数字乘法时,传统的逐位相乘算法可能不够高效。Karatsuba算法提供了一个更快的递归方法,利用分治技术来加速大数的乘法过程。这种方法基于数位分割,将大数乘法问题转换为小数乘法问题,从而减少了乘法的总次数。
解题步骤
- 分割数字:将每个数分割成两部分,例如,123456可以分割为123和456,分割点基于数的长度。
- 递归计算:
- 设两个大数为 ( 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 ) 是分割的位数。
- 合并结果:根据上述公式合并计算结果,得到最终的乘积。
代码示例
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)),使得它在多项式乘法和大整数乘法中非常有效。
解题步骤
- 表示为点值形式:将两个数表示为点值形式,这可以通过评估多项式在各点上的值实现。
- 使用FFT计算点值乘法:将两个输入数的多项式表示通过FFT转换到频域(点值形式),然后对应点值相乘。
- 逆FFT变换:将乘法后的点值结果通过逆FFT变换回系数表示形式,即得到原多项式乘法的结果。
- 处理结果和进位:将FFT的结果转换为数字,处理进位。
代码示例
这里提供一个基于FFT的多项式乘法的Python简化实现,考虑到FFT的复杂性,实际使用时可能需要依赖专门的库如numpy
或scipy
。
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库支持 - 处理进位相对复杂 |
适用场景 | - 数字较小 - 教学或简单应用 |
- 数字大小适中 - 需要比直接方法快但实现复杂度可接受 |
- 处理非常大的数字 - 高性能计算需求 |
欢迎关注微信公众号 数据分析螺丝钉