这是 LeetCode 第 43 题:
给定两个以字符串形式表示的非负整数 a 和 b,返回 a 和 b 的乘积,它们的乘积也表示为字符串形式。
初次看到此题,真是一点思路都没有,但是想到所有乘法本质上都是加法运算,那能否先转化成字符串相加的题目呢?即:
给定两个以字符串形式表示的非负整数 a 和 b,返回 a 和 b 的和,它们的和也表示为字符串形式。
这样就稍微有点思路了,步骤如下:
- 把两个需要相加的字符串位数凑成一样的
- 从后往前一位一位的加,如果有进位的话,就记下来,下一轮加上
- 别忘了单独处理首位相加进位的情况
代码如下:
function add(a, b) {
const len = Math.max(a.length, b.length) // 获取最大长度
a = a.padStart(len, '0') // 对齐
b = b.padStart(len, '0') // 对齐
let sum = '' // 两数之和
let overflow = 0 // 记录是否溢出
for (let i = len - 1; i >= 0; i--) { // 从最后一位往前计算
let ai = a.charAt(i) // 取出字符串 a 的第 i 位的值
let bi = b.charAt(i) // 取出字符串 b 的第 i 位的值
let val = +ai + +bi + overflow // 转成数字相加,同时要加上溢出位的值
if (val >= 10) { // 如果溢出就要取末尾,然后设置溢出为 1
val = val % 10
overflow = 1
} else {
overflow = 0 // 没有溢出就设置为 0
}
sum = val + sum // 把当前计算结果放到 sum 字符串的最前面,就是新的 sum
}
if (overflow) sum = '1' + sum // 特殊处理首位溢出的情况
return sum
}
由于加了详细注释,这里就不逐行解释了,可以写一段脚本验证一下:
function checkAdd(n = 10000, bound = 1000000) {
for (let i = 0; i < n; i++) {
const a = (Math.random() * bound) | 0
const b = (Math.random() * bound) | 0
const v = `${a + b}`
const s = add(`${a}`, `${b}`)
if (v !== s) {
console.log(`${a}+${b}=${s}计算有误,正确答案是${v}`)
return
}
}
console.log('都对了')
}
checkAdd()
顺利通过测试。
有了加法的基础之后,乘法就可以转换成加法了,例如 53*78 不就是 53 个 78 相加吗?于是我写出了如下的代码:
function mul(a, b) {
let sum = '0'
for (let i = 0; i < +a; i++) sum = add(sum, b)
return sum
}
然而,被 mul('123456789', '987654321') 直接搞超时了... ,看来思路不对。最终还是看了题解,觉得非常巧妙,关键下面这句话:
m位数与n位数的乘积不会超过m+n位数,所以可以定义一个长度为m+n的数组,a[i]和b[j]的乘积对应的就是arr[i+j]和arr[i+j+1]这两个位置。
AC 代码如下:
function mul(a, b) {
const aLen = a.length // 取 a 的长度
const bLen = b.length // 取 b 的长度
const arr = Array.from({length: aLen + bLen}, () => 0) // 定义长度为 a+b 的数组,全部初始化为 0
for (let i = aLen - 1; i >= 0; i--) { // 从后往前遍历 a
const ai = +a.charAt(i) // 记录 a 的第 i 位的数值
for (let j = bLen - 1; j >= 0; j--) { // 从后往前遍历 b
const bj = +b.charAt(j) // 记录 b 的第 j 位的数值
const product = ai * bj // 记录 a 的第 i 位与 b 的第 j 位的乘积
const sum = arr[i + j + 1] + product // 求和
arr[i + j + 1] = sum % 10 // 只保留最后一位
arr[i + j] += (sum / 10) | 0 // 如果溢出了,留给前一位相加用
}
}
while (arr[0] === 0) arr.shift() // 处理前面多个 0 的情况
return arr.join('') || '0' // 转换成字符串
}
AC 截图如下:
本文正在参与「掘金 3 月闯关活动」,点击查看活动详情。