力扣415题解析:字符串相加算法与多语言实现详解
发布时间:2026/8/31 5:34:02 作者:尧图编辑部 阅读量:1,286

最近在准备算法面试的同学应该都遇到过“大数相加”这类经典问题。力扣LeetCode第 415 题“字符串相加”正是这类问题的典型代表。题目看似简单但能很好地考察我们对字符串操作、进位处理以及边界条件的把控能力。很多同学在初次尝试时容易在字符与数字转换、循环终止条件或最高位进位等细节上出错。本文将围绕力扣 415. 字符串相加这道题从题目解析、思路分析、代码实现到复杂度分析进行一次完整的拆解。我们会提供多种语言Python, Java, JavaScript的清晰解法并深入探讨其中的关键技巧和易错点。无论你是刚开始刷题的新手还是想巩固基础算法的同学都能从中获得清晰的解题路径和可复用的代码模板。1. 题目背景与核心概念1.1 题目描述力扣第 415 题“字符串相加”的官方描述如下给定两个字符串形式的非负整数num1和num2计算它们的和并以字符串形式返回。注意你不能使用任何內建的用于处理大整数的库比如BigInteger也不能直接将输入的字符串转换为整数形式。num1和num2的长度都小于 5100。num1和num2都只包含数字0-9。num1和num2都不包含任何前导零除了数字0本身。示例 1输入num1 11, num2 123 输出134示例 2输入num1 456, num2 77 输出533示例 3输入num1 0, num2 0 输出01.2 问题本质与考察点这道题的核心是模拟人工竖式加法的过程。我们从小学习的加法就是从个位开始逐位相加处理进位最后得到结果。题目禁止使用大数库和直接转整数就是为了让我们手动实现这个过程。它主要考察以下几个能力字符串的基本操作如何从字符串中按位取出数字。双指针或索引的运用如何从两个字符串的末尾个位开始向前遍历。进位Carry的处理这是本题的核心逻辑需要仔细处理相加后进位值的计算与传递。边界条件处理包括两个字符串长度不同、最高位相加后产生新进位如 “9” “1” “10”、以及输入为 “0” 的情况。结果字符串的构建由于我们从个位开始计算得到的结果数字顺序是反的最后需要反转。理解这些考察点是写出健壮、高效代码的关键。2. 环境准备与解题思路2.1 解题环境说明对于算法题我们通常不需要复杂的项目环境。你只需要一个在线的力扣刷题平台或者本地的代码编辑器如 VS Code, PyCharm, IntelliJ IDEA。掌握一门编程语言的基础语法本文以 Python, Java, JavaScript 为例。理解基本的字符串和数组操作。本文的代码示例均假设在力扣的答题环境中运行即你只需要实现Solution类中的特定方法。代码可以直接复制到力扣的代码编辑器中提交。2.2 核心算法思路竖式加法模拟解决此问题的通用思路可以分解为以下几步初始化定义两个指针i和j分别指向num1和num2的末尾即个位。定义一个变量carry来存储进位值初始为0。定义一个列表或StringBuilderres来存储计算结果的每一位注意是逆序存储的。循环计算只要i 0或j 0或carry ! 0就继续循环。carry ! 0这个条件是为了处理最高位相加后仍有进位的情况例如 “999” “1”。在循环体内 a. 获取当前位数字如果指针有效0则通过ord(num1[i]) - ord(0)或int(num1[i])等方式将字符转为数字否则当前位数字视为0。 b. 计算当前位和sum digit1 digit2 carry。 c. 处理进位和当前位结果当前位结果应放入res为sum % 10。新的进位carry sum // 10。 d. 将当前位结果数字转换为字符并添加到res中。 e. 移动指针i--,j--。反转并返回结果循环结束后res中存储的是从个位到最高位的数字字符。需要将res反转然后连接成一个字符串返回。流程图示意开始 | 初始化 i, j, carry0, res[] | while (i0 或 j0 或 carry0): | digit1 num1[i] if i0 else 0 | digit2 num2[j] if j0 else 0 | total digit1 digit2 carry | carry total // 10 | res.append(str(total % 10)) | i--, j-- | 反转 res | 将 res 连接成字符串 | 返回字符串 结束3. 多语言代码实现与逐行解析下面我们分别用 Python、Java 和 JavaScript 来实现上述算法并对关键代码行进行详细解释。3.1 Python 实现Python 的字符串操作非常灵活代码也最为简洁。class Solution: def addStrings(self, num1: str, num2: str) - str: # 初始化指针和进位 i, j len(num1) - 1, len(num2) - 1 carry 0 res [] # 使用列表存储结果字符效率高于字符串拼接 # 循环条件任一字符串还有位或者还有进位 while i 0 or j 0 or carry: # 获取当前位的数字如果指针已越界则视为0 digit1 int(num1[i]) if i 0 else 0 digit2 int(num2[j]) if j 0 else 0 # 计算当前位的总和包括进位 total digit1 digit2 carry # 计算新的进位和当前位的结果 carry total // 10 digit total % 10 # 将当前位数字转为字符并加入结果列表此时是逆序 res.append(str(digit)) # 移动指针 i - 1 j - 1 # 将结果列表反转并连接成字符串 # 因为我们是按个位、十位...的顺序添加的所以需要反转 return .join(res[::-1])代码解析int(num1[i])Python 中可以直接将数字字符如5转换为整数5。res []使用列表append操作来构建结果其时间复杂度为 O(1)最后用join拼接。这比在循环中反复进行字符串拼接str str效率高得多因为字符串在 Python 中是不可变对象每次拼接都会生成新对象。while i 0 or j 0 or carry:这是循环的关键条件。or carry确保了即使两个字符串都遍历完了如果最后还有进位如“1” “9”循环还会再进行一次将进位1作为最高位加入结果。res[::-1]这是 Python 的切片语法表示将列表res完全反转。.join(...)将反转后的字符列表连接成一个完整的字符串。3.2 Java 实现Java 的实现需要更多的手动字符处理并通常使用StringBuilder来高效构建字符串。class Solution { public String addStrings(String num1, String num2) { // 初始化指针和进位 int i num1.length() - 1; int j num2.length() - 1; int carry 0; // 使用 StringBuilder 构建结果效率高 StringBuilder res new StringBuilder(); // 循环条件任一字符串还有位或者还有进位 while (i 0 || j 0 || carry 0) { // 获取当前位的数字如果指针已越界则视为0 int digit1 (i 0) ? num1.charAt(i) - 0 : 0; int digit2 (j 0) ? num2.charAt(j) - 0 : 0; // 计算当前位的总和包括进位 int sum digit1 digit2 carry; // 计算新的进位 carry sum / 10; // 计算当前位的结果 int digit sum % 10; // 将当前位数字加入 StringBuilder此时是逆序 res.append(digit); // 移动指针 i--; j--; } // 将结果反转并转换为字符串 // 因为 append 是顺序添加我们得到的是个位在前所以需要反转 return res.reverse().toString(); } }代码解析num1.charAt(i) - 0这是 Java 中将字符数字转换为整数的经典方法。字符‘0’到‘9’在 ASCII 表中是连续的‘0’的值是 48。‘5’ - ‘0’的结果就是53 - 48 5。StringBuilder在 Java 中String是不可变的。在循环中拼接字符串会产生大量临时对象影响性能。StringBuilder是可变的字符序列append操作效率很高。res.reverse().toString()StringBuilder的reverse()方法会原地反转字符序列然后toString()将其转换为String返回。循环条件carry 0与carry ! 0在此处等价因为进位值carry只可能是 0 或 1两个一位数相加最大为 99119进位最大为1。但写成carry 0更直观。3.3 JavaScript 实现JavaScript 的实现思路与 Python 和 Java 类似注意其数字转换和字符串构建方式。/** * param {string} num1 * param {string} num2 * return {string} */ var addStrings function(num1, num2) { let i num1.length - 1; let j num2.length - 1; let carry 0; const res []; // 使用数组存储结果数字 while (i 0 || j 0 || carry) { // 获取当前位的数字如果指针已越界则视为0 const digit1 i 0 ? parseInt(num1[i]) : 0; const digit2 j 0 ? parseInt(num2[j]) : 0; // 计算当前位的总和包括进位 const sum digit1 digit2 carry; // 计算新的进位和当前位的结果 carry Math.floor(sum / 10); const digit sum % 10; // 将当前位数字加入数组此时是逆序 res.push(digit); // 移动指针 i--; j--; } // 将数组反转并连接成字符串 // 因为 push 是顺序添加我们得到的是个位在前所以需要反转 return res.reverse().join(); };代码解析parseInt(num1[i])JavaScript 中parseInt可以将字符串转换为整数。num1[i]是一个字符parseInt(‘5’)得到5。也可以使用num1.charCodeAt(i) - ‘0’.charCodeAt(0)但parseInt更直观。Math.floor(sum / 10)在 JavaScript 中除法/默认返回浮点数。我们需要使用Math.floor来获取整数商即进位值。因为两个一位数相加最大为 19sum / 10的结果只能是 0 或 1Math.floor可以正确获取。res.push(digit)和res.reverse().join(‘’)使用数组push方法添加元素最后反转数组并用join方法拼接成字符串。这与 Python 的列表操作类似。4. 复杂度分析与算法评价4.1 时间复杂度我们使用了一个while循环循环的次数最多为max(len(num1), len(num2)) 11 是处理最高位进位的情况。循环体内的操作取数字、计算、追加字符都是常数时间O(1)。因此总的时间复杂度为O(max(N, M))其中 N 和 M 分别是两个输入字符串的长度。这是一个非常高效的线性时间复杂度。4.2 空间复杂度我们使用了一个额外的列表/数组/StringBuilder 来存储结果其长度最多为max(N, M) 1。除了输入和输出我们只使用了几个整型变量i,j,carry,digit1,digit2,sum。因此总的空间复杂度为O(max(N, M))主要用于存储结果字符串。这是无法避免的因为我们必须返回一个新的字符串。4.3 算法评价优点直观易懂完全模拟了人工计算加法的过程逻辑清晰。高效时间和空间复杂度都是线性的是最优解。健壮正确处理了长度不等、最高位进位、全零输入等边界情况。缺点无明显缺点是该问题的标准解法。5. 常见错误与排查思路在实现“字符串相加”时初学者常会遇到以下几个问题问题现象常见原因解决思路输出结果比预期少一位例如 “99” “1” 输出 “00”循环条件缺少对最后进位的判断。当最高位相加产生进位时循环在遍历完字符串后即停止漏掉了进位。将循环条件改为 while (i 0输出结果顺序是反的例如 “11” “123” 输出 “431”忘记反转结果。我们从个位开始计算并将结果依次存入列表得到的是逆序的字符串。在返回结果前务必对存储结果的列表或StringBuilder进行反转操作。遇到非数字字符或空字符串时报错题目已保证输入是合法数字字符串但自己测试时可能输入错误。代码未做防御性检查。对于生产代码可以在开头添加输入验证。对于算法题通常信任题目约束。确保测试用例符合题目要求。在 Java 中使用String拼接导致性能极差在循环内使用result digit result或result digit。每次操作都会创建新的String对象。务必使用StringBuilder来构建字符串。JavaScript 中进位计算错误得到小数使用sum / 10直接赋值给carry在 JavaScript 中这会得到浮点数如 0.1。使用Math.floor(sum / 10)或~~(sum / 10)来获取整数进位。Python 中结果字符串包含方括号和逗号错误地直接返回了列表res而不是拼接后的字符串。使用return .join(res[::-1])确保返回的是字符串。自检清单循环条件是否包含了carry ! 0指针越界时当前位数字是否正确地设为 0进位carry的计算是否正确total // 10或sum / 10取整当前位结果是否正确total % 10是否将数字转换成了字符再存储最终返回前是否反转了结果序列对于输入“0”和“0”是否能正确返回“0”而不是“”或[]6. 变种问题与最佳实践掌握了“字符串相加”后你可以轻松解决一系列类似问题。同时遵循一些最佳实践能让你的代码更健壮、更优雅。6.1 相关变种问题力扣 2. 两数相加这是“字符串相加”的链表版本。给你两个非空链表表示两个非负整数每位数字逆序存储。你需要返回一个同样形式的链表。解题思路完全一致只是数据结构从字符串/数组变成了链表。力扣 67. 二进制求和给你两个二进制字符串返回它们的和用二进制表示。算法一模一样只是把进制从10改为2。计算进位时carry sum // 2当前位结果为sum % 2。大数相乘力扣 43. 字符串相乘这是更复杂的题目。核心思路是模拟竖式乘法但需要嵌套循环并处理好每一层部分积的累加和进位。大数减法和除法思路类似但减法需要考虑借位处理起来比加法稍复杂。除法则是模拟竖式除法。6.2 代码最佳实践使用双指针从末尾遍历这是处理字符串/数组表示的数字计算的最标准模式。统一使用while (i 0 || j 0 || carry)作为循环条件这个条件最完备能覆盖所有情况。使用列表/StringBuilder/数组存储中间结果避免在循环中进行字符串拼接这是保证算法效率的关键。清晰命名变量使用carry(进位)、digit1/digit2(当前位数字)、sum/total(总和)、res(结果) 等有意义的变量名提高代码可读性。添加注释对于算法题清晰的注释能帮助面试官快速理解你的思路尤其是在处理进位和边界条件的地方。考虑边界用例在写完代码后主动测试以下用例“0” “0”“1” “9”(产生进位)“999” “1”(多位数进位)“123” “4567”(长度不同)手动模拟对于复杂的边界条件可以在纸上或心里手动模拟一遍算法流程确保逻辑正确。6.3 面试技巧如果这道题出现在面试中先沟通不要急于写代码。先向面试官复述题目确认理解无误例如数字是否非负是否可能为空。阐述思路说出你要模拟竖式加法使用双指针从末尾开始用一个变量记录进位。边写边讲在写代码时解释你在做什么“我现在初始化两个指针和进位变量…”“这个循环条件是为了处理最高位进位…”。写完测试写完后用1-2个简单的例子如“11” “123”和1个边界例子如“999” “1”来演示代码运行过程。分析复杂度主动分析时间和空间复杂度并说明这是最优解。7. 总结与扩展学习力扣 415 题“字符串相加”是一道非常好的入门算法题它不涉及复杂的数据结构但完整地考察了基本的编程能力循环、条件判断、数据类型转换、边界处理以及字符串/数组操作。掌握它就掌握了解决所有“大数运算”模拟题的基础框架。核心要点回顾模拟人工计算从最低位末尾开始逐位相加处理进位。循环条件三要素指针i, 指针j, 进位carry缺一不可。高效构建结果使用可变容器Python list, Java StringBuilder, JS Array存储逆序结果最后反转。小心边界长度不同的字符串、最高位的进位、全零输入。下一步学习建议巩固尝试独立完成力扣 67. 二进制求和和力扣 2. 两数相加感受算法框架的复用性。挑战尝试解决力扣 43. 字符串相乘这是大数运算的进阶版。拓展学习更多字符串相关的高频题目如反转字符串、验证回文串、字符串转换整数等。系统训练将此类“模拟”算法归入你的知识体系它通常与“数学”、“字符串”标签相关。在力扣上可以按标签或题目列表进行专项练习。算法学习是一个循序渐进的过程。从这道题出发理解其背后的“模拟”思想并能够举一反三你的解题能力就会稳步提升。多写、多练、多总结是通往算法高手的必经之路。