题解 | #大数乘法#
大数乘法
http://www.nowcoder.com/practice/c4c488d4d40d4c4e9824c3650f7d5571
题目描述
以字符串的形式读入两个数字,编写一个函数计算它们的乘积,以字符串形式返回。
(字符串长度不大于10000,保证字符串仅由'0'~'9'这10种字符组成)
方法一: 模拟
解题思路
用代码模拟2个数相乘的过程即可。整体的流程就是:把 num2 从右往左遍历,每一个数字和 num1 进行相乘, 最后把每次相乘的结果相加就得到答案。
需要注意的是每次相乘的时候,因为有可能数字非常大,超出了整型、长整型的表示范围,所以每次相乘的时候仍然需要用 字符串的形式保存结果,把每次相乘的结果相加,仍然也是 2 个字符串相加。
在写代码的过程中,注意下进位的问题及反转的问题就可以。
这里以 12 * 99 为例, 大体流程思路如下图所示:
复杂度分析
时间复杂度: O(N * M + M * max(N, M)),N 为 num1 的长度, M 为 num2 的长度。
需要遍历 num2 的每个数字,每个数字还需要和 num2 的每个数字进行相乘,所以这里复杂度为 O(N*M)。
在每次相乘结果相加的时候,也仍然需要遍历每个数字进行相加,这里遍历的次数就看 num1 和 num2 哪个字符串更长,即时间复杂度为 O(max(N, M))。
而整个过程在外面大的循环里,即 num2 的遍历循环里,所以这里复杂度为 O(M*max(N, M))。所以,最后总的时间复杂度为 O(N * M + M * max(N, M))
空间复杂度: O(N + M),N 为 num1 的长度, M 为 num2 的长度。
num1 和 num2 相乘的结果的最长长度为 N + M , 最长不会超过它,所以空间复杂度为 O(N + M)
示例代码
import java.util.*;
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
* @param num1 string字符串 第一个整数
* @param num2 string字符串 第二个整数
* @return string字符串
*/
public String solve (String num1, String num2) {
// 判断不合法的输入
if (num1 == null || num1.length() == 0 || num2 == null || num2.length() == 0) {
return null;
}
// 如果 num1 或 num2 其中有一个为 0, 则最后相乘的结果为 0
if ("0".equals(num1) || "0".equals(num2)) {
return "0";
}
char[] chars1 = num1.toCharArray();
char[] chars2 = num2.toCharArray();
// 最后的结果
String res = "0";
// 相乘之后,后面需要补零的个数
int zero = 0;
// 从右往左遍历 num2 字符串中的每一个数字, 使其和 num1 字符串相乘, 再相加,即得最后结果
for (int i = chars2.length - 1; i >= 0; i--) {
// 每一个数字和 num1 字符串相乘的结果
StringBuilder temp = new StringBuilder();
// 保存进位
int carry = 0;
// 将后面需要的零补齐
for (int j = 0; j < zero; j++) {
temp.append('0');
}
zero++;
// 取出 num2 字符串的每一个数字, 记为 y
int y = chars2[i] - '0';
// 使 y 和 num1 字符串相乘
for (int j = chars1.length - 1; j >= 0; j--) {
int x = chars1[j] - '0';
int num = x * y + carry;
temp.append(num % 10);
carry = num / 10;
}
// 如果最后进位不为 0 , 则还需要往前进 carry 位
if (carry != 0) {
temp.append(carry);
}
// 把每次相乘的结果相加起来,就是最后的结果
res = bigNumberAdd(res, temp.reverse().toString());
}
return res;
}
/**
*
* @param num1 第一个数字字符串
* @param num2 第二个数字字符串
* @return 两个字符串相加之后的结果
*/
public String bigNumberAdd(String num1, String num2) {
// 判断不合法输入
if (num1 == null || num1.length() == 0 || num2 == null || num2.length() == 0) {
return null;
}
// num1 字符串的下标, 从右往左遍历
int index1 = num1.length() - 1;
// num2 字符串的下标, 从右往左遍历
int index2 = num2.length() - 1;
// 最后相加的结果
StringBuilder res = new StringBuilder();
// 保存进位
int carry = 0;
// num1 或 num2 只要有一个字符串还有值,就继续循环
while (index1 >= 0 || index2 >= 0) {
// 拿到 num1 的一个数字
int x = index1 >= 0 ? num1.charAt(index1--) - '0' : 0;
// 拿到 num2 的一个数字
int y = index2 >= 0 ? num2.charAt(index2--) - '0' : 0;
// 拿到的 2 个数字相加,要考虑进位的情况
int num = x + y + carry;
// 该位只保存个位数
res.append(num % 10);
// 十位数要往前进一位
carry = num / 10;
}
// 如果最后进位不为 0 , 则还需要往前进 carry 位
if (carry != 0) {
res.append(carry);
}
return res.reverse().toString();
}
} 方法二: 优化方法一
解题思路
方法一就是平常我们做乘法的步骤,先相乘,再相加。
优化之后就是直接算乘法,把 num1 的每一位数字 和 num2 的每一位数字相乘, 相乘的结果放到对应的位置上,最后我们再处理相乘之后的结果即可。
如果 num1 的长度为 N , num2 的长度为 M, 则 num1 * num2 的结果最长长度为 N + M,所以开辟一个长度为 N + M 的整型数组来操作相乘的结果。
但有时候长度没有达到 N+M , 所以这里需要判断一下有没有前置零的影响,这块具体思路请参照代码。
这里以 12 * 99 为例, 大体流程如下图所示:
得到了 num 数组,之后的操作就是每个位置只保留一位数字,即处理进位:
复杂度分析
时间复杂度: O(N*M),N 为 num1 的长度, M 为 num2 的长度。
代码里有 2 层嵌套的 for 循环, 就是把 num1 的每一个数字 和 num2 的每一个数字相乘的消耗。
空间复杂度:O(N+M),N 为 num1 的长度, M 为 num2 的长度。
需要开辟一个长度为 N + M 的整型数组来操作相乘之后的结果。
示例代码
import java.util.*;
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
* @param num1 string字符串 第一个整数
* @param num2 string字符串 第二个整数
* @return string字符串
*/
public String solve(String num1, String num2) {
// 判断不合法的输入
if (num1 == null || num1.length() == 0 || num2 == null || num2.length() == 0) {
return null;
}
// 如果有一个字符串为 0 ,结果就是 0
if ("0".equals(num1) || "0".equals(num2)) {
return "0";
}
// 最后相乘的结果,长度最多就是 2 个字符串的长度相加
int[] num = new int[num1.length() + num2.length()];
// num1 的每一个数字, 和 num2 的每一个数字相乘; 放到 num 数组的对应位置上
for (int i = num1.length() - 1; i >= 0; i--) {
for (int j = num2.length() - 1; j >= 0; j--) {
// 同一个位置可能有多种情况映射过来, 所以结果位置上的结果取个累加和即可
num[i + j + 1] += (num1.charAt(i) - '0') * (num2.charAt(j) - '0');
}
}
// 如果最后的结果有进位,那么 num 数组的第一位数不为零;
// 如果没进位,则为零。这种情况需要注意不要这个零。
int end = num[0] == 0 ? 1 : 0;
StringBuilder res = new StringBuilder();
// 保存进位的值
int carry = 0;
// 从后往前遍历 num 数组,最后的结果有进位则遍历到 0 结束,没进位就遍历到 1 结束。
for (int i = num.length - 1; i >= end; i--) {
// 数组上对应位置的数 和 进位相加的结果
int temp = num[i] + carry;
// temp的个位数,才是属于对应位置上的数
res.append(temp % 10);
// temp的十位数,是属于对应位置的前一个位置上的数,即是进位的值
carry = temp / 10;
}
// 最后的进位不为零,也是计算的结果,往前进 carry 位即可。
if (carry != 0) {
res.append(carry);
}
// 根据上述的流程,把 res 倒序一下,就是最后 2 个字符串相乘的结果
return res.reverse().toString();
}
} 

阿里巴巴灵犀互娱公司福利 649人发布