当前位置: 首页 > news >正文

【华为机试】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本身。

解题思路

算法分析

这道题是大数乘法模拟手算乘法的经典应用。主要解法包括:

  1. 模拟乘法法:模拟小学乘法竖式运算过程
  2. 分治乘法法:使用Karatsuba算法进行分治
  3. FFT乘法法:使用快速傅里叶变换(复杂度最优但实现复杂)
  4. 逐位相乘法:每一位分别相乘后处理进位

问题本质分析

字符串相乘
大数乘法问题
模拟竖式乘法
分治算法
FFT算法
逐位相乘累加
Karatsuba算法
频域乘法
时间复杂度O_mn空间复杂度O_m+n
时间复杂度O_n^1.585空间复杂度O_n
时间复杂度O_nlogn空间复杂度O_n

模拟乘法算法详解

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[返回结果]

乘法竖式过程演示

输入: '123' × '456'
竖式分解
123 × 6 = 738
123 × 5 = 615(左移1位)
123 × 4 = 492(左移2位)
位置对应: 738
位置对应: 6150
位置对应: 49200
累加求和
738 + 6150 + 49200 = 56088

位置索引计算规则

位置计算规律
乘法位置规则
num1[i] × num2[j]
结果位置: i + j + 1
进位位置: i + j
个位数放入i+j+1
十位数累加到i+j
避免数组越界
正确处理进位

算法流程图

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[返回结果]

进位处理机制

进位处理
当前位累加
计算商和余数
余数保留在当前位
商加到高位
当前位 = sum % 10
高位 += sum / 10
确保单位数字
传递进位
继续下一位处理

各种解法对比

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),中间结果存储

关键优化点

优化策略
特殊值检查
数组预分配
进位优化
算法选择
零值提前返回
避免动态扩容
批量处理进位
根据数据规模选择
性能提升

实际应用场景

应用场景
密码学
金融计算
科学计算
竞赛编程
RSA加密算法
高精度金额计算
大整数运算库
ACM算法竞赛
核心算法组件

算法扩展

算法扩展
大数加法
大数减法
大数除法
大数幂运算
字符串数值运算基础
借位处理机制
长除法模拟
快速幂优化
大数运算库

测试用例设计

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

代码实现要点

  1. 数组索引设计

    • 结果数组长度为m+n
    • 乘法位置计算:i+j和i+j+1
    • 从右到左处理数字
  2. 进位处理逻辑

    • 先累加所有同位置的乘积
    • 再统一处理进位
    • 避免重复进位计算
  3. 边界条件处理

    • 零值特殊处理
    • 前导零移除
    • 数组越界检查
  4. 性能优化技巧

    • 预分配数组容量
    • 减少字符串操作
    • 选择合适的算法

手算验证示例

123 × 456手算过程
第一步
3×6=18, 3×5=15, 3×4=12
第二步
2×6=12, 2×5=10, 2×4=8
第三步
1×6=6, 1×5=5, 1×4=4
位置累加
位置0:18, 位置1:15+12, 位置2:12+10+6
位置3:8+5, 位置4:4
处理进位得到56088

这个问题的关键在于理解乘法竖式的计算规律掌握进位处理的机制,通过模拟手算过程实现高精度的字符串乘法运算。

完整题解代码

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预分配避免扩容")
}
http://www.dtcms.com/a/304471.html

相关文章:

  • 【LeetCode 随笔】
  • 【深度学习】独热编码(One-Hot Encoding)
  • 开源 Arkts 鸿蒙应用 开发(十一)证书和包名修改
  • C语言在键盘上输入一个3行3列矩阵的各个元素的值(值为整数),然后输出主对角线元素的积,并在fun()函数中输出。
  • 信号上升时间与带宽的关系
  • Leetcode-3361两个字符串的切换距离
  • FastAPI入门:请求体的字段、嵌套模型、额外数据、额外数据类型
  • Linux系统部署k8s集群
  • 在 Web3 时代通过自我主权合规重塑 KYC/AML
  • Git快速入门,完整的git项目管理工具教程,git入门到精通!
  • 青少年软件编程图形化Scratch等级考试试卷(二级)2025年6月
  • 【EDA】Calma--早期版图绘制工具商
  • python案例:基于python 神经网络cnn和LDA主题分析的旅游景点满意度分析
  • 解决mac下git pull、push需要输入密码
  • 半导体企业选用的跨网文件交换系统到底应该具备什么功能?
  • 【007TG洞察】美欧贸易新政下跨境业务的技术破局:从数据治理到智能触达的全链路重构
  • SpringBoot整合RocketMQ(rocketmq-client.jar)
  • 小程序中事件对象的属性与方法
  • IT实施方案书
  • 【dropdown组件填坑指南】—怎么实现三角箭头效果
  • 网络安全第15集
  • 河南地区危化品安全员考试题库及答案
  • 【参考】Re
  • MYSQL难面试
  • 汇总数据(使用聚集函数)
  • Element Plus
  • AI数据管家:智能体如何像“超级助手”管理企业数据?
  • 宇树 G1 部署(九)——遥操作控制脚本 teleop_hand_and_arm.py 分析与测试部署
  • 项目如何分阶段推进?几大要点分析
  • 【Linux】初识make/makefile