前言
JS最大安全整数是2的53次方减一,两个超大整数数相加、相减时往往需要借助ES11新出的BigInt。本文介绍了如果不使用BigInt来实现超大数之间的相加和相减、相乘...
1.加一 (小试牛刀😈)
- 给定一个由 整数 组成的 非空 数组所表示的非负整数,在该数的基础上加一。
- 最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。
- 你可以假设除了整数 0 之外,这个整数不会以零开头。
示例:
示例 1:
输入:digits = [1,2,3]
输出:[1,2,4]
解释:输入数组表示数字 123。
示例 2:
输入:digits = [4,3,2,1]
输出:[4,3,2,2]
解释:输入数组表示数字 4321。
示例 3:
输入:digits = [0]
输出:[1]
思考几秒,有了😹😹
- 直接把数组转成数字,加一,再转成数组。
var plusOne = function(digits) {
// 注意js计算超过最大安全整数会丢失精度
// return (String(Number(digits.join('')) +1)).split('')
// 数组拼接成字符串 => 强转 BigInt 类型 => 加一 => 强转字符串 => 拆分成数组
return (BigInt(digits.join(''))+1n).toString().split('')
};
-
js整数区间: :只有在 -2^53 + 1 到 2^53 - 1 范围内(闭区间)的整数才能在不丢失精度的情况下被表示(可通过 Number.MIN_SAFE_INTEGER 和 Number.MAX_SAFE_INTEGER 获得)
-
BigInt: 类型是 ECMAScript 2020 引入的,用于表示任意大的整数。
-
好吧!我承认,投机取巧了。
-
不!难道有现成的api不用吗?还要造轮子么?
-
面试官:不使用BigInt,如何实现?
-
我 .... 好吧~骂骂咧咧都去实现了😒😒😒
第一版🤩
- 倒序遍历数组,最后一位加一, 然后放入数组
- 考虑进位,如果 >= 10,则进位
- 需要一个变量来保存进位值,一个数组保存计算的值
- 考虑边界,遍历完到最后一项,>=10,需要补位1 如[1,9] => [2,0]
- 说白了就一个大数 +1,进位问题
/**
* @param {number[]} digits
* @return {number[]}
*/
var plusOne = function(digits=[]) {
let len = digits.length;
let resArr = [];
let carry = 1; // 进位标识
for (let i = len-1; i >=0; i--) {
let sum = digits[i] + carry;
if (sum >= 10) {
if (i===0) {
resArr.unshift(1, 0);
} else {
resArr.unshift(sum-10);
carry = 1;
}
} else{
resArr.unshift(sum);
carry = 0;
}
}
console.log(resArr)
return resArr
};
第二版🤩
- 在第一版的基础上优化
- 直接修改原数组 (少使用一个数组)
- 边界的进位,遍历结束再判断
var plusOne = function(digits=[]) {
digits = digits.slice()
let carry = 1;
for (let i = digits.length - 1; i >= 0; i--) {
let sum = digits[i] + carry;
if (sum >= 10) {
digits[i] = sum - 10;
carry = 1;
} else {
digits[i] = sum;
carry = 0;
break;
}
}
if (carry) {
digits.unshift(1);
}
return digits;
};
- 看到这里是不是感觉还行!还可以优化,第三版
- 因为我们只加1,数组里面最后一位小于9,压根不需要遍历
- 刚好进位 那就乖乖遍历咯
第三版🤩
var plusOne = function(digits = []) {
const len = digits.length;
if (len === 0) {
return [1];
}
if (digits[len - 1] < 9) {
digits[len - 1]++;
return digits;
}
let carry = 1;
for (let i = digits.length - 1; i >= 0; i--) {
let sum = digits[i] + carry;
if (sum >= 10) {
digits[i] = sum - 10;
carry = 1;
} else {
digits[i] = sum;
carry = 0;
}
}
if (carry > 0) {
digits.unshift(carry);
}
return digits;
};
其实看到这里,你是不是也想到了,还能更简,
- 判断数组是否存在9 => arr.indexOf(9) !== -1
- 存在9就需要遍历9哪里是否需要进位 哈哈~~~
还是上主菜吧!!!
2.两大整数之和👍
题目:
const str1 = '123456789';
const str2 = '98765432156464645465';
function add(str1, str2) {
return str1 + str2
}
有了上面的经验,是不是感觉简单多了。 不同点就是 1 也变成了一个大数
但是整体思路还是一样的
- 可以转成数组来遍历 或者 直接使用字符串也行
- 遍历对位相加(注意点位数可能不一样,不一样就补0)
- String.prototype.padStart()
- 需要进位,就进位
const str1 = '123456789';
const str2 = '98765432156464645465';
function add(str1='', str2='') {
if (!str1) return str2 || "0";
if (!str2) return str1 || "0";
if (str1.length > str2.length) {
str2 = str2.padStart(maxLen, '0')
} else if (str1.length < str2.length) {
str1 = str1.padStart(maxLen, '0')
}
console.log(str1,str2,maxLen)
let carry = 0
let result = []
for (let i = str1.length-1; i >=0; i--) {
const sum = Number(str1[i]) + Number(str2[i]) + carry
if (sum >= 10) {
result.unshift(sum - 10)
carry = 1
} else {
carry = 0
result.unshift(sum)
}
}
if (carry) {
result.unshift(1)
}
console.log(result);
return result.join('')
}
解析:
- 判断两字符串长度,取最大值,小的前面补0(不会影响大小)
- 声明一个进位标识、一个结果数组
- 遍历字符串,从后往前,每一位相加
- 如果大于10,就取余数,并设置进位标识为;同时存进新数组
- 处理遍历结束后的进位标识
- 数组拼接成字符串并返回结果
3. 两大整数之差👍
思路:
还是先补0,保证位数一致遍历字符串,从后往前,对位相减如果小于0,就 +10,并设置进位标识为 -1 => 借位;同时存进新数组处理遍历结束后的进位标识如果为 -1 说明计算结果为负数,需要补一个负号数组拼接成字符串并返回结果
感谢评论区大量指正:上面确实考虑不周:
- a>b => a-b ; 如 2-1 => 2-1=1
- a<b => -(b-a) 如 1-2 => -(2-1)
- 可以先判断a 和 b 哪个大,长度长者大,长度一致者,取第一位比较
- 定义计算函数 sub => a-b 还是 b-a
- 借位逻辑不变
- 最后判断是否需要添加 '-' 号
- 细心的小伙伴肯定发现:如果数带了负号,还有问题👻
- 如果带了正负号,还需要加一些逻辑,题目两个大数 所以忽略了负号情况👀
- 有兴趣可以下来补上(核心不变,判断长度、第一位是否为‘-’,再决定sub函数)
function subtract(str1='', str2='') {
if (!str1) return str2 || "0";
if (!str2) return str1 || "0";
let carry = 0;
let isMin = false;
const result = [];
let sub = (a,b) => a-b;
if (str1.length > str2.length) {
str2 = str2.padStart(str1.length, '0');
} else {
if (str1.slice(0, 1) <= str2.slice(0, 1)) {
isMin = true;
sub = (a,b) => b-a;
}
str1 = str1.padStart(str2.length, '0');
}
for (let i = str1.length-1; i >= 0; i--) {
const sum = sub(str1[i], str2[i]) + carry;
if (sum>=0) {
carry = 0;
result.unshift(sum);
} else {
carry = -1;
result.unshift(sum + 10);
}
}
let res = result.join('').replace(/^0+/, '')
if (res === '') return 0
if (isMin) {
res = '-' + res;
}
return res;
}
4. 两大整数之积
解析:
- 两数之积的长度 一定小于等于 两数的长度之和
- 声明一个数组,长度为两数之和
- 双重循环 ,从后往前,对位相乘,并把结果存进新数组
- 需要进位 则前一位 +1
- 数组转字符串 去除开头0
function multiply(num1: string, num2: string): string {
if (+num1 == 0 || +num2 == 0) {
return '0'
}
num1 = num1.replace(/^0*/g, '');
num2 = num2.replace(/^0*/g, '');
let len1 = num1.length;
let len2 = num2.length;
let res: number[] = new Array(len1 + len2).fill(0);
for (let i = num1.length-1; i >= 0; i--) {
for (let j = num2.length-1; j >=0; j--) {
let sum = Number(num1[i]) * Number(num2[j]) + res[i + j + 1];
res[i + j + 1] = sum % 10;
res[i + j] += Math.floor(sum / 10);
}
}
return res.join('').replace(/^0*/, '') || '0';
}
结语:
如果本文对你有收获,麻烦动动发财的小手,点点关注、点点赞!!!👻👻👻
因为收藏===会了
如果有不对、更好的方式实现、可以优化的地方欢迎在评论区指出,谢谢👾👾👾