字符串相加
问题
给定两个以字符串形式表示的非负整数 num1 和 num2,返回它们的和,也以字符串形式表示。不能使用任何内建大数库或直接转换为整数。
示例:
输入:num1 = "456", num2 = "77"
输出:"533"
为什么需要大数运算?
JavaScript 的 Number 类型最大安全整数是 (约 9 × 10^15),超过就会丢失精度。前端处理订单号、身份证号等长数字时经常遇到。
答案
方法一:模拟竖式加法(推荐)
从末位开始逐位相加,处理进位:
addStrings.ts
function addStrings(num1: string, num2: string): string {
let i = num1.length - 1;
let j = num2.length - 1;
let carry = 0;
const result: string[] = [];
while (i >= 0 || j >= 0 || carry > 0) {
const n1 = i >= 0 ? num1.charCodeAt(i) - 48 : 0; // '0' 的 ASCII = 48
const n2 = j >= 0 ? num2.charCodeAt(j) - 48 : 0;
const sum = n1 + n2 + carry;
result.push(String(sum % 10)); // 当前位
carry = Math.floor(sum / 10); // 进位
i--;
j--;
}
return result.reverse().join('');
}
图解:
4 5 6
+ 7 7
─────────
carry: 0
6 + 7 + 0 = 13 → 写 3, carry = 1
5 + 7 + 1 = 13 → 写 3, carry = 1
4 + 0 + 1 = 5 → 写 5, carry = 0
result = ['3','3','5'] → reverse → "533"
复杂度分析
- 时间复杂度: — m、n 为两个字符串长度
- 空间复杂度: — 存储结果
方法二:使用数组下标(另一种写法)
addStringsAlt.ts
function addStrings(num1: string, num2: string): string {
// 确保 num1 是较长的
if (num1.length < num2.length) [num1, num2] = [num2, num1];
const arr1 = num1.split('').map(Number);
const arr2 = num2.split('').map(Number);
// 对齐:在 arr2 前面补零
while (arr2.length < arr1.length) arr2.unshift(0);
let carry = 0;
for (let i = arr1.length - 1; i >= 0; i--) {
const sum = arr1[i] + arr2[i] + carry;
arr1[i] = sum % 10;
carry = Math.floor(sum / 10);
}
if (carry > 0) arr1.unshift(carry);
return arr1.join('');
}
常见面试追问
Q1: 如何实现字符串相乘?(LeetCode 43)
答案:模拟竖式乘法,num1[i] * num2[j] 的结果累加到 result[i+j] 和 result[i+j+1] 位置:
function multiply(num1: string, num2: string): string {
if (num1 === '0' || num2 === '0') return '0';
const m = num1.length, n = num2.length;
const result = new Array(m + n).fill(0);
for (let i = m - 1; i >= 0; i--) {
for (let j = n - 1; j >= 0; j--) {
const mul = (num1.charCodeAt(i) - 48) * (num2.charCodeAt(j) - 48);
const p1 = i + j; // 高位
const p2 = i + j + 1; // 低位
const sum = mul + result[p2];
result[p2] = sum % 10;
result[p1] += Math.floor(sum / 10);
}
}
// 去掉前导零
let start = 0;
while (start < result.length - 1 && result[start] === 0) start++;
return result.slice(start).join('');
}
Q2: BigInt 和手写大数的区别?
答案:
| 特性 | BigInt (原生) | 手写大数 |
|---|---|---|
| 性能 | 引擎优化,更快 | 纯 JS 实现,较慢 |
| 兼容性 | IE 不支持 | 全兼容 |
| 面试 | 了解即可 | 需要掌握原理 |
| 序列化 | JSON 不支持 | 天然是字符串 |
// BigInt 用法
const a = BigInt('123456789012345678901234567890');
const b = 123n; // 字面量
const sum = a + b; // BigInt 只能和 BigInt 运算
Q3: 前端大数场景有哪些?
答案:
- 后端返回 Long 型 ID:JSON 中的数字超过安全整数(如雪花 ID),需要后端以字符串返回
- 金融计算:金额计算需要精度
- 身份证号/手机号:虽然是数字但应作为字符串处理
// 常见问题:JSON.parse 精度丢失
JSON.parse('{"id": 9007199254740993}');
// { id: 9007199254740992 } ← 精度丢失了!
// 解决方案:正则替换后再用 BigInt 处理
const jsonStr = '{"id": 9007199254740993}';
const fixed = jsonStr.replace(/:\s*(\d{16,})/g, ': "$1"');
JSON.parse(fixed); // { id: "9007199254740993" }