415. 字符串相加(Add Strings)
频次 ★★★★ · 难度 🟢 · 高频:字节/腾讯/美团/阿里
题目
给定两个非负整数(以字符串形式给出),计算它们的和并同样以字符串形式返回。不能使用 BigInteger 或直接将输入转为整数。
示例:
输入: "11", "123"
输出: "134"
输入: "456", "77"
输出: "533"
输入: "0", "0"
输出: "0"
思路
双指针从右向左逐位模拟竖式加法:两个指针 i、j 分别指向两个字符串的末尾,用 carry 记录进位。每次取出当前位的数字(如果指针已到头则取 0),相加后 % 10 为当前位结果,/ 10 为进位。最后如果还有进位需要在最前面补 1。
因为是从低位向高位计算,每位的数字要加到结果字符串的头部(StringBuilder.insert(0, ...)),或者先追加到 StringBuilder 末尾再整体反转。
代码
public String addStrings(String num1, String num2) {
StringBuilder sb = new StringBuilder();
int i = num1.length() - 1, j = num2.length() - 1;
int carry = 0;
while (i >= 0 || j >= 0 || carry != 0) {
int digit1 = (i >= 0) ? num1.charAt(i) - '0' : 0;
int digit2 = (j >= 0) ? num2.charAt(j) - '0' : 0;
int sum = digit1 + digit2 + carry;
sb.append(sum % 10);
carry = sum / 10;
i--;
j--;
}
return sb.reverse().toString();
}复杂度
- 时间:O(max(m, n)) — m、n 分别是两个字符串的长度
- 空间:O(max(m, n)) — 结果字符串的长度(不算输入)
边界条件
- 两个字符串都是
"0":循环一次后sum = 0,carry = 0,结果"0"。 - 一个为空(题目说了非负整数,不会为空串,但防御性考虑):如果允许空串,当作
"0"处理即可。 - 不同长度:如
"999" + "1",短字符串走完后只剩长的部分和进位carry,循环条件中的i >= 0 || j >= 0保证继续处理剩余位数。 - 最高位产生进位:如
"9" + "1" = "10",循环条件中的carry != 0保证最后多输出一位"1"。
变式
- 二进制相加(67. 二进制求和):思路完全一样,只是逢 2 进 1。
- 字符串相乘(43. 字符串相乘):需要每一位相乘后错位相加,是本题的扩展。
- 链表版两数相加:见 2. 两数相加(逆序存储,正序还需先反转)。
易错点
- 字符转数字要用
num.charAt(i) - '0',不是Integer.parseInt或(int) num.charAt(i)(后者是 ASCII 码)。 - 结果用
sb.reverse()而不是每算出一位就sb.insert(0, ...)——后者每次插入是 O(n) 操作,整体变成 O(n²)。 - 循环条件不能漏掉
carry != 0:所有位数都处理完但最后还有进位时,需要多生成一位。“9”+“1” 就是典型例子。 - 指针
i、j每轮循环都要减 1,不能遗漏(容易忘记在条件分支中减)。
面试追问
- 字符串相加和链表相加(如 2. 两数相加)的本质区别是什么? 字符串高位在左、低位在右,只能从右向左模拟竖式,最后要反转结果;链表版因为是逆序存储,个位在链表头,从左到右遍历就是低位到高位,不用反转。两者的核心都是”逐位计算 + carry 进位 + 收尾进位”三段式。
- 如果字符串可能包含前导零(如 “00123”),需要处理吗? 题目通常约定”非负整数不包含前导零,除非数字本身就是 0”。如果前导零可能出现,一种做法是计算结果后去掉前导零(但注意 “0” 本身要保留),另一种是计算时不受影响(前导零不影响加法的正确性)。
- 如果要支持浮点数字符串相加,该怎么做? 先找到小数点位置,把整数部分和小数部分分别对齐(小数部分补零到长度一致),然后分别做整数加法和小数加法,最后处理小数部分向整数部分的进位。