【华为机试】43. 字符串相乘
文章目录
- 43. 字符串相乘
- 描述
- 示例 1
- 示例 2
- 提示
- 解题思路
- 算法分析
- 问题本质分析
- 模拟乘法算法详解
- 乘法竖式过程演示
- 位置索引计算规则
- 算法流程图
- 进位处理机制
- 各种解法对比
- Karatsuba分治算法
- 边界情况处理
- 时间复杂度分析
- 空间复杂度分析
- 关键优化点
- 实际应用场景
- 算法扩展
- 测试用例设计
- 代码实现要点
- 手算验证示例
- 完整题解代码
43. 字符串相乘
描述
给定两个以字符串形式表示的非负整数 num1 和 num2,返回 num1 和 num2 的乘积,它们的乘积也表示为字符串形式。
注意:不能使用任何内置的 BigInteger 库或直接将输入转换为整数。
示例 1
输入: num1 = “2”, num2 = “3”
输出: “6”
示例 2
输入: num1 = “123”, num2 = “456”
输出: “56088”
提示
- 1 <= num1.length, num2.length <= 200
- num1 和 num2 只能由数字组成。
- num1 和 num2 都不包含任何前导零,除了数字0本身。
解题思路
算法分析
这道题是大数乘法和模拟手算乘法的经典应用。主要解法包括:
- 模拟乘法法:模拟小学乘法竖式运算过程
- 分治乘法法:使用Karatsuba算法进行分治
- FFT乘法法:使用快速傅里叶变换(复杂度最优但实现复杂)
- 逐位相乘法:每一位分别相乘后处理进位
问题本质分析
模拟乘法算法详解
flowchart TDA[输入num1和num2] --> B[处理特殊情况]B --> C{是否包含0}C -->|是| D[返回"0"]C -->|否| E[初始化结果数组]E --> F[双重循环遍历]F --> G[计算单位乘积]G --> H[累加到对应位置]H --> I[处理进位]I --> J[继续下一位]J --> K{是否完成所有位}K -->|否| FK -->|是| L[处理最终进位]L --> M[转换为字符串]M --> N[去除前导零]N --> O[返回结果]
乘法竖式过程演示
位置索引计算规则
算法流程图
flowchart TDA[开始] --> B[特殊情况检查]B --> C{num1或num2为"0"}C -->|是| D[返回"0"]C -->|否| E[创建结果数组]E --> F[外层循环i: num1从右到左]F --> G[内层循环j: num2从右到左]G --> H[计算乘积: digit1 × digit2]H --> I[累加到result[i+j+1]]I --> J[处理进位到result[i+j]]J --> K{内层循环结束?}K -->|否| GK -->|是| L{外层循环结束?}L -->|否| FL -->|是| M[构造结果字符串]M --> N[跳过前导零]N --> O[返回结果]
进位处理机制
各种解法对比
graph TDA[解法对比] --> B[模拟乘法]A --> C[分治算法]A --> D[FFT算法]A --> E[字符串加法组合]B --> F[时间O_mn空间O_m+n]C --> G[时间O_n^1.585空间O_logn]D --> H[时间O_nlogn空间O_n]E --> I[时间O_mn²空间O_mn]F --> J[简单直观推荐]G --> K[大数优化]H --> L[理论最优复杂实现]I --> M[易理解效率低]
Karatsuba分治算法
flowchart TDA[Karatsuba算法] --> B[分割数字]B --> C[x = x1×10^m + x0]B --> D[y = y1×10^m + y0]C --> E[递归计算]D --> EE --> F[z0 = x0 × y0]E --> G[z2 = x1 × y1]E --> H[z1 = (x0+x1) × (y0+y1) - z0 - z2]F --> I[组合结果]G --> IH --> II --> J[result = z2×10^2m + z1×10^m + z0]
边界情况处理
graph TDA[边界情况] --> B[零值处理]A --> C[单位数字]A --> D[极大数字]A --> E[相同数字]B --> F["0" × 任何数 = "0"]C --> G[直接计算后转字符串]D --> H[200位×200位=400位]E --> I[数字平方运算]F --> J[提前返回优化]G --> JH --> K[数组容量预分配]I --> K
时间复杂度分析
- 模拟乘法:O(m×n),m和n分别为两数字长度
- Karatsuba:O(n^log₂3) ≈ O(n^1.585)
- FFT乘法:O(n log n),n为较大数字长度
- 字符串累加:O(m×n²),效率最低
空间复杂度分析
- 模拟乘法:O(m+n),结果数组空间
- Karatsuba:O(log n),递归栈空间
- FFT乘法:O(n),变换数组空间
- 字符串累加:O(m×n),中间结果存储
关键优化点
实际应用场景
算法扩展
测试用例设计
graph TDA[测试用例] --> B[基础功能]A --> C[边界情况]A --> D[性能测试]B --> E[小数相乘]B --> F[大数相乘]B --> G[不同长度]C --> H[零值情况]C --> I[单位数]C --> J[最大长度]D --> K[200位×200位]D --> L[相同数字平方]E --> M[验证正确性]F --> MG --> MH --> MI --> MJ --> MK --> N[验证性能]L --> N
代码实现要点
-
数组索引设计:
- 结果数组长度为m+n
- 乘法位置计算:i+j和i+j+1
- 从右到左处理数字
-
进位处理逻辑:
- 先累加所有同位置的乘积
- 再统一处理进位
- 避免重复进位计算
-
边界条件处理:
- 零值特殊处理
- 前导零移除
- 数组越界检查
-
性能优化技巧:
- 预分配数组容量
- 减少字符串操作
- 选择合适的算法
手算验证示例
这个问题的关键在于理解乘法竖式的计算规律和掌握进位处理的机制,通过模拟手算过程实现高精度的字符串乘法运算。
完整题解代码
package mainimport ("fmt""strconv""strings""time"
)// 解法一:模拟乘法竖式(推荐解法)
// 时间复杂度:O(m×n),空间复杂度:O(m+n)
func multiply(num1 string, num2 string) string {// 特殊情况:任一数字为0if num1 == "0" || num2 == "0" {return "0"}m, n := len(num1), len(num2)// 结果最多有 m+n 位result := make([]int, m+n)// 从右到左遍历两个数字for i := m - 1; i >= 0; i-- {digit1 := int(num1[i] - '0')for j := n - 1; j >= 0; j-- {digit2 := int(num2[j] - '0')// 计算乘积并加到对应位置product := digit1 * digit2posLow := i + j + 1 // 个位posHigh := i + j // 十位// 累加到当前位sum := product + result[posLow]result[posLow] = sum % 10 // 保留个位result[posHigh] += sum / 10 // 进位到高位}}// 转换为字符串,跳过前导零var sb strings.Builderstart := 0for start < len(result) && result[start] == 0 {start++}for i := start; i < len(result); i++ {sb.WriteByte(byte(result[i] + '0'))}return sb.String()
}// 解法二:优化的模拟乘法(预处理优化)
// 时间复杂度:O(m×n),空间复杂度:O(m+n)
func multiplyOptimized(num1 string, num2 string) string {if num1 == "0" || num2 == "0" {return "0"}m, n := len(num1), len(num2)result := make([]int, m+n)// 预转换字符为数字,减少重复转换digits1 := make([]int, m)digits2 := make([]int, n)for i := 0; i < m; i++ {digits1[i] = int(num1[i] - '0')}for i := 0; i < n; i++ {digits2[i] = int(num2[i] - '0')}// 执行乘法for i := m - 1; i >= 0; i-- {for j := n - 1; j >= 0; j-- {product := digits1[i] * digits2[j]posLow := i + j + 1posHigh := i + jsum := product + result[posLow]result[posLow] = sum % 10result[posHigh] += sum / 10}}// 构建结果字符串var sb strings.Buildersb.Grow(m + n) // 预分配容量start := 0for start < len(result) && result[start] == 0 {start++}for i := start; i < len(result); i++ {sb.WriteByte(byte(result[i] + '0'))}return sb.String()
}// 解法三:字符串加法组合
// 时间复杂度:O(m×n²),空间复杂度:O(m×n)
func multiplyByAddition(num1 string, num2 string) string {if num1 == "0" || num2 == "0" {return "0"}result := "0"// 逐位相乘并累加for i := len(num2) - 1; i >= 0; i-- {digit := int(num2[i] - '0')// 计算 num1 × digittemp := multiplyByDigit(num1, digit)// 添加相应的零(位数偏移)zeros := len(num2) - 1 - ifor j := 0; j < zeros; j++ {temp += "0"}// 累加到结果result = addStrings(result, temp)}return result
}// 辅助函数:数字字符串乘以单个数字
func multiplyByDigit(num string, digit int) string {if digit == 0 {return "0"}result := make([]int, len(num)+1)carry := 0for i := len(num) - 1; i >= 0; i-- {product := int(num[i]-'0')*digit + carryresult[i+1] = product % 10carry = product / 10}result[0] = carry// 转换为字符串var sb strings.Builderstart := 0if result[0] == 0 {start = 1}for i := start; i < len(result); i++ {sb.WriteByte(byte(result[i] + '0'))}return sb.String()
}// 辅助函数:字符串加法
func addStrings(num1 string, num2 string) string {i, j := len(num1)-1, len(num2)-1carry := 0var result strings.Builderfor i >= 0 || j >= 0 || carry > 0 {sum := carryif i >= 0 {sum += int(num1[i] - '0')i--}if j >= 0 {sum += int(num2[j] - '0')j--}result.WriteByte(byte(sum%10 + '0'))carry = sum / 10}// 反转结果return reverseString(result.String())
}// 辅助函数:反转字符串
func reverseString(s string) string {runes := []rune(s)for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {runes[i], runes[j] = runes[j], runes[i]}return string(runes)
}// 解法四:Karatsuba分治算法(适用于超大数)
// 时间复杂度:O(n^1.585),空间复杂度:O(log n)
func multiplyKaratsuba(num1 string, num2 string) string {if num1 == "0" || num2 == "0" {return "0"}// 小数字直接计算if len(num1) <= 10 && len(num2) <= 10 {return multiply(num1, num2)}// 补齐到相同长度maxLen := max(len(num1), len(num2))if maxLen%2 == 1 {maxLen++}num1 = padLeft(num1, maxLen)num2 = padLeft(num2, maxLen)return karatsuba(num1, num2)
}func karatsuba(x, y string) string {n := len(x)if n <= 10 {return multiply(x, y)}m := n / 2// 分割x1 := x[:n-m] // 高位x0 := x[n-m:] // 低位y1 := y[:n-m] // 高位y0 := y[n-m:] // 低位// 递归计算z0 := karatsuba(x0, y0)z2 := karatsuba(x1, y1)// 计算 (x1 + x0) * (y1 + y0)x1PlusX0 := addStrings(x1, x0)y1PlusY0 := addStrings(y1, y0)z1Temp := karatsuba(x1PlusX0, y1PlusY0)// z1 = z1Temp - z2 - z0z1 := subtractStrings(z1Temp, addStrings(z2, z0))// 组合结果: z2 * 10^(2m) + z1 * 10^m + z0result := addStrings(addStrings(multiplyByPowerOf10(z2, 2*m),multiplyByPowerOf10(z1, m)),z0)return removeLeadingZeros(result)
}// 辅助函数:左填充零
func padLeft(s string, length int) string {if len(s) >= length {return s}return strings.Repeat("0", length-len(s)) + s
}// 辅助函数:乘以10的幂
func multiplyByPowerOf10(num string, power int) string {if num == "0" || power == 0 {return num}return num + strings.Repeat("0", power)
}// 辅助函数:字符串减法(假设num1 >= num2)
func subtractStrings(num1, num2 string) string {if compareStrings(num1, num2) < 0 {return "0" // 简化处理,实际应该处理负数}i, j := len(num1)-1, len(num2)-1borrow := 0var result strings.Builderfor i >= 0 {diff := int(num1[i]-'0') - borrowif j >= 0 {diff -= int(num2[j] - '0')j--}if diff < 0 {diff += 10borrow = 1} else {borrow = 0}result.WriteByte(byte(diff + '0'))i--}return removeLeadingZeros(reverseString(result.String()))
}// 辅助函数:比较两个数字字符串
func compareStrings(num1, num2 string) int {if len(num1) != len(num2) {return len(num1) - len(num2)}return strings.Compare(num1, num2)
}// 辅助函数:移除前导零
func removeLeadingZeros(s string) string {i := 0for i < len(s) && s[i] == '0' {i++}if i == len(s) {return "0"}return s[i:]
}// 辅助函数:取最大值
func max(a, b int) int {if a > b {return a}return b
}// 解法五:直接转换法(受整数范围限制,仅用于演示)
func multiplyDirect(num1 string, num2 string) string {// 仅适用于小数字,受int64范围限制if len(num1) > 18 || len(num2) > 18 {return multiply(num1, num2) // 回退到模拟算法}n1, _ := strconv.ParseInt(num1, 10, 64)n2, _ := strconv.ParseInt(num2, 10, 64)return strconv.FormatInt(n1*n2, 10)
}// 测试函数
func testMultiply() {testCases := []struct {num1 stringnum2 stringexpected stringdesc string}{{"2", "3", "6", "简单单位数相乘"},{"123", "456", "56088", "三位数相乘"},{"0", "123", "0", "零乘法"},{"999", "999", "998001", "最大单位数相乘"},{"12", "34", "408", "两位数相乘"},{"100", "200", "20000", "整百数相乘"},{"11", "11", "121", "相同数字相乘"},{"1", "123456789", "123456789", "乘以1"},{"999999999", "999999999", "999999998000000001", "九位数相乘"},{"12345", "67890", "838102050", "五位数相乘"},{"1000000", "2000000", "2000000000000", "大整数相乘"},{"98765", "43210", "4267635650", "随机数字"},{"777", "888", "689976", "重复数字"},{"505", "404", "204020", "包含零的数字"},{"99999", "99999", "9999800001", "五个9相乘"},}fmt.Println("=== 字符串相乘测试 ===\n")for i, tc := range testCases {// 测试主要解法result1 := multiply(tc.num1, tc.num2)result2 := multiplyOptimized(tc.num1, tc.num2)result3 := multiplyByAddition(tc.num1, tc.num2)status := "✅"if result1 != tc.expected {status = "❌"}fmt.Printf("测试 %d: %s\n", i+1, tc.desc)fmt.Printf("输入: \"%s\" × \"%s\"\n", tc.num1, tc.num2)fmt.Printf("期望: %s\n", tc.expected)fmt.Printf("模拟法: %s\n", result1)fmt.Printf("优化法: %s\n", result2)fmt.Printf("加法法: %s\n", result3)fmt.Printf("结果: %s\n", status)fmt.Println(strings.Repeat("-", 40))}
}// 性能测试
func benchmarkMultiply() {fmt.Println("\n=== 性能测试 ===\n")// 构造测试数据testData := []struct {num1, num2 stringdesc string}{{generateNumber(10), generateNumber(10), "10位×10位"},{generateNumber(50), generateNumber(50), "50位×50位"},{generateNumber(100), generateNumber(100), "100位×100位"},{generateNumber(200), generateNumber(200), "200位×200位"},}algorithms := []struct {name stringfn func(string, string) string}{{"模拟乘法", multiply},{"优化乘法", multiplyOptimized},{"Karatsuba", multiplyKaratsuba},{"加法组合", multiplyByAddition},}for _, data := range testData {fmt.Printf("%s:\n", data.desc)for _, algo := range algorithms {start := time.Now()result := algo.fn(data.num1, data.num2)duration := time.Since(start)fmt.Printf(" %s: 结果长度%d, 耗时: %v\n",algo.name, len(result), duration)}fmt.Println()}
}// 生成指定长度的随机数字字符串
func generateNumber(length int) string {if length <= 0 {return "0"}var sb strings.Buildersb.Grow(length)// 首位不能为0sb.WriteByte(byte('1' + length%9))// 后续位可以是任意数字for i := 1; i < length; i++ {sb.WriteByte(byte('0' + (i*7+3)%10))}return sb.String()
}// 演示手算验证
func demonstrateManualCalculation() {fmt.Println("\n=== 手算过程演示 ===")fmt.Println("计算 123 × 456:")fmt.Println()fmt.Println(" 123")fmt.Println(" × 456")fmt.Println(" -----")fmt.Println(" 738 (123 × 6)")fmt.Println(" 6150 (123 × 50)")fmt.Println(" 49200 (123 × 400)")fmt.Println(" -----")fmt.Println(" 56088")fmt.Println()result := multiply("123", "456")fmt.Printf("算法结果: %s\n", result)fmt.Printf("验证: %s\n", map[bool]string{true: "✅ 正确", false: "❌ 错误"}[result == "56088"])
}func main() {fmt.Println("43. 字符串相乘 - 多种解法实现")fmt.Println("========================================")// 基础功能测试testMultiply()// 性能对比测试benchmarkMultiply()// 手算演示demonstrateManualCalculation()// 展示算法特点fmt.Println("\n=== 算法特点分析 ===")fmt.Println("1. 模拟乘法:经典解法,时间O(m×n),推荐日常使用")fmt.Println("2. 优化乘法:预处理优化,减少重复计算,性能略优")fmt.Println("3. Karatsuba:分治算法,时间O(n^1.585),适合超大数")fmt.Println("4. 加法组合:思路直观,但效率较低O(m×n²)")fmt.Println("5. 直接转换:受整数范围限制,仅适用小数字")fmt.Println("\n=== 关键技巧总结 ===")fmt.Println("• 位置计算:num1[i]×num2[j] 结果放在 i+j+1 位置")fmt.Println("• 进位处理:先累加同位置乘积,再统一处理进位")fmt.Println("• 零值优化:任一数字为0时提前返回")fmt.Println("• 前导零处理:结果构造时跳过前导零")fmt.Println("• 容量预分配:StringBuilder预分配避免扩容")
}