题目链接:力扣
解题思路:题目要求不能使用任何内置的 BigInteger 库或直接将输入转换为整数。所以可以模拟小学学过的竖式乘法进行运算。比如123*456
1 2 3
4 5 6
---------
7 3 8
6 1 5
4 9 2
---------
5 6 0 8 8
可以将乘数456的每一位与被乘数123的每一位相乘,然后将相乘的结果累加,注意每次累加之前都需要将加数向左移动一位与被加数相加。比如被加数738和加数615相加,需要将加数615左移一位,相当于加数最右边补0变为6150与738相加。
具体的算法步骤如下:
- 如过num1或者num2其中任意一个等于"0",直接返回字符串"0"
- result="0"保存结果,carry保存进位(满10进1),preZero=0保存加数左移的位数,每轮循环后自增1
- 从右往左循环遍历num2的每一位:
- builder=new StringBuilder()保存相乘的结果
- builder中追加perZero个0(相当于最右边加数补0)
- x = num2.charAt(i) - '0',被乘数的当前位
- 从右往左循环遍历num1的每一位:
- y = num1.charAt(j) - '0' , 乘数的当前位
- 本轮相乘的结果multi = x*y+carry
- carry = multi/10;
- builder.append(multi%10)
- 内层循环结束后,如果carry不等于0,需要把进位加上,builder.append(carry),carry=0
- add(result,builder.toString),两个字符串相加的函数,注意:这里的builder.toString的结果为低位在前,高位在后。
- preZero++,每一轮加数都要比上一轮加数大10被,即多补一个0
- new StringBuilder(result).reverse().toString()即所求
两个字符串相加add(String first,String second)函数实现:
- 因为传递过来的fist和second都是低位在前,高位在后,所以只需要从左往右遍历first和second字符串的每一位,令其相加,满10进1就可以了,循环的次数为first和second字符串长度的最大值,如果当前遍历的索引超过了其中一个字符串的长度,这个较短的字符串相当于贡献的加数为0,这里注意以下就可以了,避免索引越界
AC代码:
public class Solution {
public static String multiply(String num1, String num2) {
if (num1.equals("0") || num2.equals("0")) {
return "0";
}
String result ="0";
int carry = 0;
int preZero = 0;
for (int i = num2.length() - 1; i >= 0; i--) {
StringBuilder builder = new StringBuilder();
for (int k = 0; k < preZero; k++) {
builder.append("0");
}
int x = (num2.charAt(i) - '0');
for (int j = num1.length() - 1; j >= 0; j--) {
int y = (num1.charAt(j) - '0');
int multi = x * y + carry;
carry = multi / 10;
builder.append(multi % 10);
}
if (carry != 0) {
builder.append(carry);
carry = 0;
}
result = add(result, builder.toString());
preZero++;
}
return new StringBuilder(result).reverse().toString();
}
public static String add(String first, String second) {
int firstLen = first.length();
int secondLen = second.length();
StringBuilder result = new StringBuilder();
int maxLen = Math.max(firstLen, secondLen);
int carry = 0;
for (int i = 0; i < maxLen; i++) {
int x = i >= firstLen ? 0 : first.charAt(i) - '0';
int y = i >= secondLen ? 0 : second.charAt(i) - '0';
int add = x+y+carry;
carry=add/10;
result.append(add%10);
}
if (carry!=0){
result.append(carry);
}
return result.toString();
}
}
优化:上述解法中有较多的字符串相加操作,效率比较低,可以使用一个int数组保存最终的结果的每一位
使用数组保存最终的结果时所需要的两个前提:
- 乘数num2的位数为m,被乘数num1的位数为n,则num1*num2的结果的最大位数为m+n,最小位数为m+m-1
- 比如m=2,n=2时,num1和num2都取最小值10,则num1*num2=100,相乘结果位数为3,都取最大值时99*99=9801,结果位数为4,所以最大位数为m+n,最小值为m+n-1。
- 所以结果数组res的数组长度设置为m+n一定是可以,要么全部使用,要么只会浪费一个空间
- num1[i] * num2[j] 的结果 tem 在res存储的位置是固定的(假设从结果数组res的最后一位开始存储,低位存储在最右边),tem的位数只会有两种形式,要么一位,要么是两位,其中一位可以看作两位的特殊形式,高位补为0(即,0y或者xy),其中个数存储在res[i+j+1]中,十位存储在res[i+j]中,在存储的时候,需要与原位置中的数值进行相加,如下所示
1 | 2 | 3 |
* 4 | 5 | 6 |
--------------
7 | 3 | 8 |
6 | 1 | 5 |
4 | 9 | 2 |
--------------------
5 | 6 | 0 | 8 | 8
res索引[0 1 2 3 4 5]
比如num1[2]=3,num2[2]=6,num1[2]*num2[2]=18,结果18的个位8保存在res[2+2+1],即res[5]中,十位1保存在res[4]中,这里的1会和其他保存在res[4]的结果相加,最终res[4]=8
最终结果就是res数组转为字符串,注意如果res[0]=0,要去除前导0
AC代码
class Solution {
public static String multiply(String num1, String num2) {
if (num1.equals("0") || num2.equals("0")) {
return "0";
}
int[] res = new int[num1.length() + num2.length()];
for (int i = num2.length() - 1; i >= 0; i--) {
int x = num2.charAt(i) - '0';
for (int j = num1.length() - 1; j >= 0; j--) {
int y = num1.charAt(j) - '0';
int result = res[i + j + 1] + x * y;
res[i + j + 1] = result % 10;
res[i + j] += result / 10;
}
}
int start = res[0] != 0 ? 0 : 1;
StringBuilder builder = new StringBuilder();
for (int i =start;i<res.length;i++){
builder.append(res[i]);
}
return builder.toString();
}
}