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

字符串相乘:从暴力算法到规律优化

文章目录

  • 字符串相乘:从暴力算法到规律优化
    • 一、问题引入:为什么需要特殊处理?
    • 二、朴素思路:模仿手工乘法
      • 2.1 手工乘法的步骤
      • 2.2 代码实现(初始版本)
      • 2.3 初始版本的问题分析
    • 三、优化思路:减少字符串操作
      • 3.1 优化字符串加法
      • 3.2 优化乘法中的补零操作
    • 四、进一步优化:基于数学规律的算法
      • 4.1 乘积长度的数学规律
      • 4.2 位相乘的位置规律
      • 4.3 优化版本代码
    • 五、最终版本解析
      • 5.1 核心思路
      • 5.2 时间和空间复杂度
      • 5.3 优势总结
    • 六、测试用例验证

字符串相乘:从暴力算法到规律优化

在这里插入图片描述

题目链接

一、问题引入:为什么需要特殊处理?

LeetCode 第 43 题要求我们实现两个非负整数的相乘,其中输入和输出都以字符串形式表示,且不能使用内置的大数库或直接转换为整数。这个问题的核心挑战在于:

  • 输入数字可能非常大(长度可达 200 位),远超任何内置整数类型的存储范围
  • 必须手动模拟乘法运算的全过程
  • 需要处理中间结果的进位和拼接

那如何从最朴素的思路开始,逐步推导出高效解法?

二、朴素思路:模仿手工乘法

2.1 手工乘法的步骤

回忆一下我们在纸上做乘法的过程:

  1. 用第一个数的每一位分别乘以第二个数
  2. 每完成一次乘法,根据当前位的位置补 0(例如个位乘完补 0 个 0,十位乘完补 1 个 0)
  3. 把所有中间结果相加,得到最终乘积

例如计算 “123” × “456”:

  • 3 × 456 = 1368
  • 2 × 456 = 912 → 补 1 个 0 → 9120
  • 1 × 456 = 456 → 补 2 个 0 → 45600
  • 总和:1368 + 9120 + 45600 = 56088

2.2 代码实现(初始版本)

基于这个思路,我们可以写出第一版代码:

#include <string>
#include <algorithm>
using namespace std;class Solution {
public:string multiply(string num1, string num2) {// 特殊情况:任何一方为0,结果为0if(num1 == "0" || num2 == "0") {return "0";}int len1 = num1.size();int len2 = num2.size();string res = "0";  // 初始结果为0// 用num1的每一位乘以num2for(int i = len1 - 1; i >= 0; i--) {string temp;  // 存储当前位的乘法结果int carry = 0;int numi = num1[i] - '0';  // 转换为数字// 计算numi × num2for(int j = len2 - 1; j >= 0; j--) {int numj = num2[j] - '0';int product = numi * numj + carry;temp.push_back((product % 10) + '0');  // 存储当前位carry = product / 10;  // 计算进位}// 处理剩余进位if(carry != 0) {temp.push_back(carry + '0');}// 反转得到正确的顺序(因为我们是从低位开始计算的)reverse(temp.begin(), temp.end());// 根据当前位的位置补0(第i位补len1-1-i个0)int zeros = len1 - 1 - i;while(zeros--) {temp.push_back('0');}// 累加当前结果到总结果res = addStrings(res, temp);}return res;}// 辅助函数:字符串加法string addStrings(string a, string b) {int lena = a.size() - 1;int lenb = b.size() - 1;string res;int carry = 0;// 从低位到高位相加while(lena >= 0 || lenb >= 0 || carry > 0) {int na = (lena >= 0) ? (a[lena--] - '0') : 0;int nb = (lenb >= 0) ? (b[lenb--] - '0') : 0;int sum = na + nb + carry;res.insert(0, 1, (sum % 10) + '0');  // 插入到结果头部carry = sum / 10;  // 更新进位}return res;}
};

2.3 初始版本的问题分析

这个版本虽然能正确工作,但存在明显的效率问题:

  1. 字符串插入效率低res.insert(0, ...) 每次都需要移动所有字符,时间复杂度为 O (n)
  2. 中间字符串频繁创建:每次乘法和加法都会创建新的字符串,增加了内存开销
  3. 整体时间复杂度高:假设两个字符串长度分别为 m 和 n,时间复杂度为 O (m×n + m²×n)(乘法 O (m×n),加法 O (m×n) 且需要加 m 次)

三、优化思路:减少字符串操作

3.1 优化字符串加法

字符串加法中,insert(0, ...) 操作效率低下,我们可以改为从后往前存储结果,最后再反转:

// 优化后的字符串加法
string addStrings(string a, string b) {int lena = a.size() - 1;int lenb = b.size() - 1;string res;int carry = 0;while(lena >= 0 || lenb >= 0 || carry > 0) {int na = (lena >= 0) ? (a[lena--] - '0') : 0;int nb = (lenb >= 0) ? (b[lenb--] - '0') : 0;int sum = na + nb + carry;res.push_back((sum % 10) + '0');  // 先存到尾部carry = sum / 10;}reverse(res.begin(), res.end());  // 最后反转return res;
}

这个优化将每次插入的 O (n) 操作变为 O (1)(尾部插入),最后反转一次 O (n),整体效率提升明显。

3.2 优化乘法中的补零操作

在乘法中,补零操作可以更高效:不需要实际插入字符,而是在加法时考虑偏移量。但为了保持逻辑清晰,我们暂时保留补零操作,后续会有更好的优化。

四、进一步优化:基于数学规律的算法

4.1 乘积长度的数学规律

观察可知:两个长度分别为 m 和 n 的数字相乘,结果的长度最多为 m + n(例如 999 × 99 = 98901,3 位 × 2 位 = 5 位)。

这个规律让我们可以预先分配一个固定长度的数组来存储结果,避免频繁的字符串操作。

4.2 位相乘的位置规律

对于 num1 [i](从右数第 i 位)和 num2 [j](从右数第 j 位)的乘积,其结果会影响最终结果的第 i+j 位和第 i+j+1 位(从右数,从 0 开始)。为什么会这样?那是因为两数相乘结果不会超过三位数,所以智慧影响两个位置!

例如 “123” × “456” 中:

  • num1 [0] = ‘3’(个位),num2 [0] = ‘6’(个位)
  • 乘积 3×6=18,影响结果的第 0+0=0 位(8)和第 0+0+1=1 位(1)

4.3 优化版本代码

string multiply(string num1, string num2) {if (num1 == "0" || num2 == "0") {return "0";}int m = num1.size(), n = num2.size();// 结果最多m+n位vector<int> resArr(m + n, 0);// 计算每一位的乘积并累加到结果数组for (int i = m - 1; i >= 0; i--) {int digit1 = num1[i] - '0';for (int j = n - 1; j >= 0; j--) {int digit2 = num2[j] - '0';int product = digit1 * digit2;// 计算当前乘积在结果数组中的位置int p1 = i + j, p2 = i + j + 1;// 累加当前乘积到结果数组int sum = product + resArr[p2];resArr[p2] = sum % 10;  // 当前位resArr[p1] += sum / 10;  // 进位累加到高位}}// 转换为字符串(跳过前导零)string res;for (int num : resArr) {// 跳过开头的0,但不能全跳(至少保留一个0)if (!(res.empty() && num == 0)) {res.push_back(num + '0');}}return res;
}

五、最终版本解析

5.1 核心思路

  1. 预分配结果数组:利用乘积长度规律,创建 m+n 大小的数组
  2. 位运算累加:直接在数组中累加每对位的乘积结果,避免中间字符串
  3. 最后转字符串:跳过前导零,将数组转换为最终字符串

5.2 时间和空间复杂度

  • 时间复杂度:O (m×n),其中 m 和 n 分别是两个输入字符串的长度,我们只需要遍历一次所有位的组合
  • 空间复杂度:O (m+n),用于存储结果数组

5.3 优势总结

  1. 效率极高:避免了所有字符串插入操作和中间结果的字符串存储
  2. 逻辑清晰:直接模拟了乘法的数学本质
  3. 内存友好:使用数组存储中间结果,内存开销稳定

六、测试用例验证

  1. 基础测试:num1 = “2”, num2 = “3”
    • 结果数组大小为 2+1=3,初始为 [0,0,0]
    • 计算 2×3=6,p1=0+0=0,p2=1
    • sum=6+0=6 → resArr[1]=6, resArr[0]=0
    • 转换为字符串:跳过前导零 → “6”
  2. 复杂测试:num1 = “123”, num2 = “456”
    • 结果数组大小为 3+3=6
    • 经过多轮计算后,数组变为 [0,5,6,0,8,8]
    • 转换为字符串:“56088”
http://www.dtcms.com/a/512270.html

相关文章:

  • 突破性技术:DeepSeek-OCR通过光学压缩解决大语言模型长上下文挑战
  • wap的网站模板百度运营优化师
  • 网站项目框架1688货源网一件代销
  • 小白python入门 - 4. Python 中的变量:从基础概念到编程思维的构建
  • 日语学习-日语知识点小记-构建基础-JLPT-N3阶段-二阶段(5):文法運用
  • 表格识别技术:将纸质信息转化为可分析的结构化数据,推动智能办公革命
  • 读懂 YOLOv4:兼顾速度与精度的目标检测王者
  • 磁悬浮轴承控制方法全景解析:从经典策略到智能前沿
  • 响应式网站无法做百度联盟wordpress无中断音乐插件
  • AURIX-TC3xx-GTM详解三-CMU(Clock Management Unit)
  • 文件速览软件对比丨Quicklook与PowerToys速览功能对比
  • 网络编程-通信协议
  • 湖州做网站公司哪家好网络规划设计师下午考点汇总
  • pip install gptqmodel报错:error: subprocess-exited-with-error
  • 消息中间件4.VPC
  • Linux时间轮定时器
  • 怎样做ppt建网站网站开辟两学一做专栏
  • 昆凌做的广告买化妆品网站微信应用小程序
  • ps免费模板网站360建筑网官网怎么登录
  • 高速摄像机在精密制造领域的应用
  • Docker入门:快速部署你的第一个Web应用
  • 《从 0 到 1 毫秒:用 Rust + Axum 0.8 打造支持 HTTP/3 的零拷贝文件服务器》
  • 【linux】多线程(六)生产者消费者模型,queue模拟阻塞队列的生产消费模型
  • 网站界面设计起着决定性作用软件开发外包是什么意思
  • YOLO26:面向实时目标检测的关键架构改进与性能基准测试
  • Debezium日常分享系列之:Debezium 3.3.1.Final发布
  • 织梦栏目页不显示网站描述wordpress能采集
  • Android Studio新手开发第二十五天
  • 网站服务公司案例遵义新蓝外国语学校网站建设
  • Selenium+Java(22):解决Windows系统中,Jenkins控制台打印乱码问题