gh_mirrors/leet/leetcode项目:字符串相乘题解分析
你还在为处理超大数字相乘时遇到溢出问题而烦恼吗?当面对无法用常规整数类型存储的大数字字符串相乘时,如何高效准确地计算结果?本文将通过gh_mirrors/leet/leetcode项目中的实现,详细解析两种字符串相乘的方法,帮助你轻松解决这一难题。读完本文,你将了解到大数乘法的基本原理、两种不同的实现思路以及它们的优缺点对比。
问题描述与挑战
字符串相乘(Multiply Strings)问题要求将两个用字符串表示的非负整数相乘,并返回结果的字符串形式。这一问题的核心挑战在于处理可能非常大的数字,这些数字无法用常规的整数或长整数类型存储,因此需要通过字符串操作和模拟手工乘法的过程来实现。
相关题目定义可参考项目中的C++/chapImplement.tex文件,其中详细描述了这一问题的要求和约束。
解决方案概述
项目中提供了两种主要的实现方法来解决字符串相乘问题:
- 基础方法:将每个字符转换为对应的整数,形成一个整数数组,然后模拟手工乘法的过程进行计算。
- 优化方法:将9个字符对应一个64位整数,减少数组元素的数量,从而提高计算效率。
下面我们将详细解析这两种方法的实现思路和代码。
基础方法:字符转整数数组实现
实现思路
基础方法的核心思想是将输入的两个数字字符串转换为整数数组,其中每个数组元素对应一位数字。然后,模拟手工乘法的过程,将第二个数的每一位与第一个数相乘,并将结果累加到对应的位置上,最后处理进位并将结果转换回字符串形式。
代码实现
// LeetCode, Multiply Strings
// @author 连城 (http://weibo.com/lianchengzju)
// 一个字符对应一个int
// 时间复杂度O(n*m),空间复杂度O(n+m)
typedef vector<int> bigint;
bigint make_bigint(string const& repr) {
bigint n;
transform(repr.rbegin(), repr.rend(), back_inserter(n),
[](char c) { return c - '0'; });
return n;
}
string to_string(bigint const& n) {
string str;
transform(find_if(n.rbegin(), prev(n.rend()),
[](char c) { return c > '\0'; }), n.rend(), back_inserter(str),
[](char c) { return c + '0'; });
return str;
}
bigint operator*(bigint const& x, bigint const& y) {
bigint z(x.size() + y.size());
for (size_t i = 0; i < x.size(); ++i)
for (size_t j = 0; j < y.size(); ++j) {
z[i + j] += x[i] * y[j];
z[i + j + 1] += z[i + j] / 10;
z[i + j] %= 10;
}
return z;
}
class Solution {
public:
string multiply(string num1, string num2) {
return to_string(make_bigint(num1) * make_bigint(num2));
}
};
方法解析
-
字符串转整数数组:
make_bigint函数将输入的字符串转换为一个整数数组,其中数组的每个元素对应字符串中的一位数字,并且数组的顺序是反转的(即字符串的最低位对应数组的第一个元素)。 -
整数数组相乘:
operator*函数实现了两个大整数数组的乘法。它创建了一个结果数组z,其大小为两个输入数组大小之和。然后,通过双重循环,将第二个数的每一位与第一个数的每一位相乘,并将结果累加到z数组的相应位置上。同时处理进位,将当前位的进位加到下一位。 -
整数数组转字符串:
to_string函数将计算得到的整数数组转换回字符串形式。它首先找到数组中第一个非零元素,然后从该元素开始,将后续的每个数字转换为字符,并拼接成最终的结果字符串。
这种方法的时间复杂度为O(n*m),其中n和m分别是两个输入字符串的长度。空间复杂度为O(n+m),主要用于存储结果数组。
优化方法:9字符对应一个64位整数
实现思路
优化方法的核心思想是将9个字符(即9位数字)合并为一个64位整数,这样可以显著减少数组元素的数量,从而提高计算效率。由于 10^9 * 10^9 = 10^18,而2^63-1约为9.2*10^18,因此一个64位整数可以安全地存储9位数字的乘积。
代码实现
// LeetCode, Multiply Strings
// 9个字符对应一个int64_t
// 时间复杂度O(n*m/81),空间复杂度O((n+m)/9)
/** 大整数类. */
class BigInt {
public:
/**
* @brief 构造函数,将字符串转化为大整数.
* @param[in] s 输入的字符串
* @return 无
*/
BigInt(string s) {
vector<int64_t> result;
result.reserve(s.size() / RADIX_LEN + 1);
for (int i = s.size(); i > 0; i -= RADIX_LEN) { // [i-RADIX_LEN, i)
int temp = 0;
const int low = max(i - RADIX_LEN, 0);
for (int j = low; j < i; j++) {
temp = temp * 10 + s[j] - '0';
}
result.push_back(temp);
}
elems = result;
}
/**
* @brief 将整数转化为字符串.
* @return 字符串
*/
string toString() {
stringstream result;
bool started = false; // 用于跳过前导0
for (auto i = elems.rbegin(); i != elems.rend(); i++) {
if (started) { // 如果多余的0已经都跳过,则输出
result << setw(RADIX_LEN) << setfill('0') << *i;
} else {
result << *i;
started = true; // 碰到第一个非0的值,就说明多余的0已经都跳过
}
}
if (!started) return "0"; // 当x全为0时
else return result.str();
}
/**
* @brief 大整数乘法.
* @param[in] x x
* @param[in] y y
* @return 大整数
*/
static BigInt multiply(const BigInt &x, const BigInt &y) {
vector<int64_t> z(x.elems.size() + y.elems.size(), 0);
for (size_t i = 0; i < y.elems.size(); i++) {
for (size_t j = 0; j < x.elems.size(); j++) { // 用y[i]去乘以x的各位
// 两数第i, j位相乘,累加到结果的第i+j位
z[i + j] += y.elems[i] * x.elems[j];
if (z[i + j] >= BIGINT_RADIX) { // 看是否要进位
z[i + j + 1] += z[i + j] / BIGINT_RADIX; // 进位
z[i + j] %= BIGINT_RADIX;
}
}
}
while (z.back() == 0) z.pop_back(); // 没有进位,去掉最高位的0
return BigInt(z);
}
private:
typedef long long int64_t;
/** 一个数组元素对应9个十进制位,即数组是亿进制的
* 因为 1000000000 * 1000000000 没有超过 2^63-1
*/
const static int BIGINT_RADIX = 1000000000;
const static int RADIX_LEN = 9;
/** 万进制整数. */
vector<int64_t> elems;
BigInt(const vector<int64_t> num) : elems(num) {}
};
class Solution {
public:
string multiply(string num1, string num2) {
BigInt x(num1);
BigInt y(num2);
return BigInt::multiply(x, y).toString();
}
};
方法解析
-
BigInt类定义:BigInt类封装了大整数的存储和运算。它使用一个vector<int64_t>来存储大整数,其中每个元素代表9位十进制数字。
-
构造函数:构造函数将输入的字符串转换为BigInt对象。它从字符串的末尾开始,每9个字符为一组,将每组字符转换为一个整数,并存储在elems向量中。
-
toString方法:该方法将BigInt对象转换回字符串形式。它遍历elems向量中的元素,将每个元素转换为字符串,并确保每个元素(除了可能的第一个元素)都被格式化为9位数字(不足9位的前面补0)。
-
multiply静态方法:该方法实现了两个BigInt对象的乘法运算。它创建了一个结果向量z,其大小为两个输入向量大小之和。然后,通过双重循环,将第二个数的每个元素与第一个数的每个元素相乘,并将结果累加到z向量的相应位置上。如果某个位置的累加结果超过了BIGINT_RADIX(即10^9),则进行进位处理。最后,移除结果向量末尾的任何多余零,并返回一个新的BigInt对象。
这种优化方法的时间复杂度约为O(n*m/81),空间复杂度约为O((n+m)/9),相比基础方法有了显著的提升。
两种方法的对比分析
为了更清晰地比较两种方法的特点,我们可以通过以下表格进行总结:
| 方法 | 核心思想 | 时间复杂度 | 空间复杂度 | 优点 | 缺点 |
|---|---|---|---|---|---|
| 基础方法 | 每个字符对应一个int | O(n*m) | O(n+m) | 实现简单直观,易于理解 | 数组元素数量多,运算次数多,效率较低 |
| 优化方法 | 9个字符对应一个int64_t | O(n*m/81) | O((n+m)/9) | 数组元素数量少,运算次数少,效率高 | 实现相对复杂,需要处理分组和格式化问题 |
从表格中可以看出,优化方法在时间和空间效率上都明显优于基础方法。特别是当处理非常大的数字(即输入字符串很长)时,优化方法的优势会更加明显。
总结与展望
本文详细介绍了gh_mirrors/leet/leetcode项目中两种字符串相乘的实现方法。基础方法通过将每个字符转换为整数数组来模拟手工乘法,实现简单直观;优化方法则通过将9个字符合并为一个64位整数,显著提高了计算效率。
无论是哪种方法,都成功地解决了大数字相乘时的溢出问题,并且通过巧妙的设计,模拟了手工乘法的过程。这些方法不仅适用于LeetCode上的字符串相乘问题,还可以应用于其他需要处理大整数运算的场景。
未来,我们可以进一步探索更多的优化策略,例如使用Karatsuba算法等更高效的乘法算法,以进一步提高大整数相乘的效率。同时,也可以考虑将这些方法扩展到其他基本运算(如除法、取模等),以构建一个完整的大整数运算库。
希望本文能够帮助你更好地理解字符串相乘的实现原理和优化方法。如果你对项目中的其他题解感兴趣,可以查阅项目的README.md文件,获取更多信息。如果你有任何问题或建议,欢迎在项目的评论区留言讨论。
点赞、收藏、关注三连,获取更多优质算法解析和编程技巧!下期我们将为大家带来LeetCode中另一个经典问题的深度解析,敬请期待!
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



