问题描述
Given two non-negative integers num1 and num2 represented as strings, return the product of num1 and num2.
Note:
The length of both num1 and num2 is < 110.
Both num1 and num2 contains only digits 0-9.
Both num1 and num2 does not contain any leading zero.
You must not use any built-in BigInteger library or convert the inputs to integer directly.
分析
两个整数相乘,并不是什么很复杂的过程,而且问题规模不大,两个数都不超过110位,因此尝试用代码模拟出运算过程即可。
多位数乘多位数可以归约到多位数乘一位数和多位数加多位数。不如先模拟这两个运算的过程以供调用。
多位数乘一位数如下:
string mulNum(const string a, int k) {
string ans(a.size(), '0');
int carry = 0;
for (int i = a.size()-1; i >= 0; --i) {
int t = a[i]-'0';
ans[i] = (t*k+carry)%10+'0';
carry = (t*k+carry)/10;
}
if (carry != 0)
ans.insert(ans.begin(), carry+'0');
return ans;
}
多位数加多位数如下:
string add(const string num1, const string num2) {
int n = num1.size();
int m = num2.size();
int max = ((n > m) ? n : m);
string sum(max, '0');
int carry = 0;
for (int i = 1; i <= max; ++i) {
int a = ((n-i >= 0) ? num1[n-i]-'0' : 0);
int b = ((m-i >= 0) ? num2[m-i]-'0' : 0);
sum[max-i] = (a+b+carry)%10+'0';
carry = (a+b+carry)/10;
}
if (carry != 0)
sum.insert(sum.begin(), carry+'0');
return sum;
}
最后总的过程如下:
string multiply(string num1, string num2) {
int n = num1.size();
int m = num2.size();
string res("0");
for (int i = m-1; i >= 0; --i) {
string temp = mulNum(num1, num2[i]-'0');
for (int j = 0; j < m-1-i; ++j) {
temp.insert(temp.end(), '0');
}
res = add(res, temp);
}
size_t st = res.find_first_not_of('0');
if (string::npos != st)
return res.substr(st);
return "0";
}
可以说上述代码毫无亮点。稍稍值得注意的不过是要将多位数乘一位数的结果移位,以及末尾要去除答案前无意义的0。如果不加去零操作,对于【 “9133”, “0”】这组测试,会输出”0000”,而不是正确的”0”。
时间复杂度O(
换一种打开方式
前面的做法中规中矩,下面看看另一种优雅的解决方式,它的原理很简单又很巧妙。
两个一位数相乘,结果最多是个两位数。经过简单地印证,可以发现,分别隶属两个整数的两个数字的乘积对结果有影响的位置,是可以由它们在原来整数里的位置确定的。如下图1所示:
即 num1[i] * num2[j]
will be placed at indices [i + j
, i + j + 1]
既然这样,那么我们便不需要用那么多的辅助字符串来存储中间结果,可以直接在最终字符串上操作。代码2如下:
string multiply(string num1, string num2) {
string sum(num1.size() + num2.size(), '0');
for (int i = num1.size() - 1; 0 <= i; --i) {
int carry = 0;
for (int j = num2.size() - 1; 0 <= j; --j) {
int tmp = (sum[i + j + 1] - '0') + (num1[i] - '0') * (num2[j] - '0') + carry;
sum[i + j + 1] = tmp % 10 + '0';
carry = tmp / 10;
}
sum[i] += carry;
}
size_t startpos = sum.find_first_not_of("0");
if (string::npos != startpos) {
return sum.substr(startpos);
}
return "0";
}
大胆的想法
关于大数乘法,可用快速傅里叶变换(FFT)。
对于n阶多项式乘法,中规中矩的方法复杂度是O(
傅里叶方法通过将多项式的系数表示转换为点值表示来计算乘法所得多项式。
其中转换复杂度为O(
而将数展开为十进制表示后可视为多项式,例如
其中细节很复杂,涉及到复数,对本题规模及整数运算来说可能有精度问题。
- 图片源自yavinci ↩
- 代码源自chiangkaishrek ↩