LeetCode/字符串相乘
给定两个以字符串形式表示的非负整数 num1 和 num2,返回 num1 和 num2 的乘积,它们的乘积也表示为字符串形式。
扩充(字符串相加)
class Solution {
public:
string addStrings(string num1, string num2) {
int i = num1.length() - 1, j = num2.length() - 1, add = 0;//这里是从后往前,不必要转过来再从前往后
string ans = "";
while (i >= 0 || j >= 0 || add != 0) {//没加完或者有进位继续循环
int x = i >= 0 ? num1[i] - '0' : 0;//这里通过补0统一操作
int y = j >= 0 ? num2[j] - '0' : 0;//这里通过补0统一操作
int result = x + y + add;
ans.push_back('0' + result % 10);//倒序插入、方便内存动态扩充
add = result / 10;//判断是否进位
i -= 1;//num1指针前移
j -= 1;//num2指针前移
}
// 计算完以后的答案需要翻转过来
reverse(ans.begin(), ans.end());
return ans;
}
};
1. 补0错位求和
先计算每个字符和整条字符串的乘积,注意进位,倒序放入,方便内存扩充,然后考虑如何将每个字符与字符串的乘积加起来,
这里是在遍历字符的时候,从后往前,所以一开始对用于存放结果的字符串补0即可,随着遍历趟数增加,补充0的数目也逐渐增加
补充的0逻辑上相当于放在了低位,实现累加时的错位
错位求和
class Solution {
public:
string multiply(string num1, string num2) {
if (num1 == "0" || num2 == "0")
return "0";//0乘任何数为0
string ans = "0";//当前错位累加和,初始值为0
int m = num1.size(), n = num2.size();
for (int i = n - 1; i >= 0; i--) {//从num2最后一位开始,遍历一次是与num1全部相乘的一个结果
string curr;//当前
int add = 0;
for (int j = n - 1; j > i; j--)//往低位补0实现错位,每遍历一次多补一个0
curr.push_back(0);
int y = num2.at(i) - '0';//从后往前取num2整型
//遍历完即实现一个字符与字符串相乘
for (int j = m - 1; j >= 0; j--) {//从后往前取num1,其运算逻辑本来也是从低位到高位
int x = num1.at(j) - '0';//转换num1整型
int product = x * y + add;//运算出一位相乘的结果
curr.push_back(product % 10);//存储
add = product / 10;//记录进位
}
//如果最后有进位,记得补上
while (add != 0) {
curr.push_back(add % 10);
add /= 10;
}
reverse(curr.begin(), curr.end());//反转过来该求和,得到真正的一次结果
for (auto &c : curr) {
c += '0';}
ans = addStrings(ans, curr);//字符串求和
}
return ans;
}
string addStrings(string &num1, string &num2)//实现字符串相加功能,倒序加再动态扩充,注意进位,实现见开头
};
2. 直接存放
num1[i]与num2[j]的乘积会在最后结果的num[i+j+1]位,所以直接将位与位的计算结果存储即可
先乘积求和存储,最后全部运算完再考虑进位问题,这里就需要再倒序了,因为内存已经确定
class Solution {
public:
string multiply(string num1, string num2) {
if (num1 == "0" || num2 == "0") return "0";//零与任何数乘积为0
int m = num1.size(), n = num2.size();//记录两数长度
auto ansArr = vector<int>(m + n);//直接分配数组空间
//全遍历消耗O(m×n)时间复杂度
for (int i = m - 1; i >= 0; i--) {//从后往前遍历num1中每一个数
int x = num1.at(i) - '0';//转换成整形
for (int j = n - 1; j >= 0; j--) {//从后往前遍历num2中每一个数
int y = num2.at(j) - '0';//转换成整形
ansArr[i + j + 1] += x * y;//记录乘积结果
}
}
//再单独整理成只含0~9范围的数,前面遍历过程无法整理
for (int i = m + n - 1; i > 0; i--) {
ansArr[i - 1] += ansArr[i] / 10;//超出部分进一位
ansArr[i] %= 10;//原位置保留小于10的数
}
int index = ansArr[0] == 0 ? 1 : 0;//记录数进位后最终截止位置
string ans;
while (index < m + n) {
ans.push_back(ansArr[index]);
index++;
}
for (auto &c: ans) c += '0';
return ans;
}
};
3. 快速傅里叶卷积运算
这里相当于是对方法三进行优化,方法三相当于求了两字符串乘积运算时,两字符串各位对影响最终结果每一位的乘积求和
ansArr[i + j + 1] = num1[i] * num2[j],多分配一位为了存储最后可能的进位
其实也就是Ci=∑akbi-k,实质上,乘法的结果序列c也就是序列a与序列b的卷积