直接将String转为数字相乘会超过int和long的范围,因此需要拆解乘法过程来得到答案
一、时间复杂度改良
本题什么时候进位有两种处理,后者较好,能降低算法时间复杂度
1. 每两个数字相乘都在结果中进位
优点:最后得到的res矩阵能直接代表计算结果,从前往后遍历并append就能得到所需字符串
缺点:每次计算乘积后都要进位,即进位操作在双层循环中,大幅度增加时间复杂度
2. 每次计算保留每两个数字相乘的原始结果,最后进位
优点:统一处理进位,在单层循环中,降低算法时间复杂度
缺点:最后必须从后往前处理res矩阵来进位,构造字符串需要用到insert方法来使后面遍历到的数字插在头部
二、空间复杂度改良
本题用可以用矩阵or数组存放乘法拆解的中间计算结果
1. 矩阵
优点:更直观、容易想到,每两个数字相乘的结果放在矩阵的一个格子中
缺点:空间消耗大,且之后处理矩阵中的结果又需要一个双层循环,不仅增大空间复杂度,还增大了时间复杂度
2. 数组
优点:二维降一维,降低空间复杂度,且乘法拆解计算的过程中就完成了每一列计算结果的累加
缺点:思维上复杂一点,要想到可以累加每一步的计算结果
/**
* 自己不会做
* 该思路拆解除法过程,得到两个数字每一位与每一位相乘的结果,保存在矩阵中
* 得到矩阵再最后统一进位、表示为字符串,这样能省去每一步都要进位或要字符串相加的麻烦
* Runtime: 3 ms, faster than 90.66%
* Memory Usage: 39.1 MB, less than 45.44%
*/
class Solution {
public String multiply(String num1, String num2) {
if (num1.equals("0") || num2.equals("0")) {
return "0";
}
char[] arr1 = num1.toCharArray(), arr2 = num2.toCharArray();
int row = arr2.length, col = arr1.length + arr2.length - 1;
int[][] res_matrix = new int[row][col]; // matrix to store intermediate results during multiplication
for (int i = 0; i < arr1.length; i++) { // calculate the 1st row to avoid make accumulation simpler afterwards
res_matrix[0][i] = (arr1[i] - '0') * (arr2[0] - '0');
}
for (int i = 1; i < arr2.length; i++) { // calculate and accumulate the results in the same col
for (int k = 0; k < i; k++) {
res_matrix[i][k] = res_matrix[i - 1][k];
}
for (int j = 0; j < arr1.length; j++) {
res_matrix[i][i + j] = (arr1[j] - '0') * (arr2[i] - '0') + res_matrix[i - 1][i + j];
}
}
StringBuilder res = new StringBuilder();
for (int i = col - 1; i > 0; i--) { // carry bit at once
res.insert(0, res_matrix[row - 1][i] % 10); // lengthen res from the head
res_matrix[row - 1][i - 1] += res_matrix[row - 1][i] / 10;
}
res.insert(0, res_matrix[row - 1][0]);
return res.toString();
}
}
/**
* 和上面同样的做法,但写法改良了
* 可以只用一维的结果数组,每次更新都直接累加现有的计算结果
*/
class Solution {
public String multiply(String num1, String num2) {
if (num1.equals("0") || num2.equals("0")) {
return "0";
}
char[] arr1 = num1.toCharArray(), arr2 = num2.toCharArray();
int[] res = new int[arr1.length + arr2.length - 1]; // array to keep intermediate results during multiplication process
for (int i = 0; i < arr1.length; i++) {
for (int j = 0; j < arr2.length; j++) {
res[i + j] += (arr1[i] - '0') * (arr2[j] - '0'); // accumulate
}
}
StringBuilder sb = new StringBuilder();
for (int i = res.length - 1; i > 0; i--) {
sb.insert(0, res[i] % 10);
res[i - 1] += res[i] / 10;
}
sb.insert(0, res[0]);
return sb.toString();
}
}
/**
* 仍然和前两个同样的思路,但是每次计算出结果都进位
* 同样能得到正确答案但是时间复杂度大,运行时间也长了
*/
class Solution {
public String multiply(String num1, String num2) {
if (num1.equals("0") || num2.equals("0")) {
return "0";
}
char[] arr1 = num1.toCharArray(), arr2 = num2.toCharArray();
int[] res = new int[arr1.length + arr2.length - 1];
for (int i = arr1.length - 1; i >= 0; i--) {
for (int j = arr2.length - 1; j >= 0; j--) {
res[i + j] += (arr1[i] - '0') * (arr2[j] - '0');
if (i != 0 || j != 0) {
res[i + j - 1] += res[i + j] / 10;
res[i + j] %= 10;
}
}
}
StringBuilder sb = new StringBuilder();
for (int i : res) {
sb.append(i);
}
return sb.toString();
}
}
/**
* 思路稍微有变化,把一对单个数字的乘积看作两位数,从后往前计算并持续进位
* 十位和个位分别改变res数组的 i+j 和 i+j+1 两个位置
* 这个方法在最后需要先判断第一位是否为0,为0不能append
* Runtime: 7 ms, faster than 39.55%
* Memory Usage: 39.2 MB, less than 46.63%
*/
class Solution {
public String multiply(String num1, String num2) {
if (num1.equals("0") || num2.equals("0")) {
return "0";
}
char[] arr1 = num1.toCharArray(), arr2 = num2.toCharArray();
int[] res = new int[arr1.length + arr2.length]; // take every multiplication of two num as 2 digits
for (int i = arr1.length - 1; i >= 0; i--) {
for (int j = arr2.length - 1; j >= 0; j--) {
int mul = (arr1[i] - '0') * (arr2[j] - '0');
int p1 = i + j, p2 = i + j + 1, temp = mul + res[p2];
res[p2] = temp % 10; // carry digits
res[p1] += temp / 10; // carry digits
}
}
StringBuilder sb = new StringBuilder();
if (res[0] != 0) {
sb.append(res[0]);
}
for (int i = 1; i < res.length; i++) {
sb.append(res[i]);
}
return sb.toString();
}
}