1. 题目背景与需求解析
LetCode第二题是一道经典的链表相加问题,题目编号通常为#2,标题为"Add Two Numbers"。这道题在技术面试中出现频率极高,据不完全统计,在头部科技公司的算法面试中出现概率超过60%。题目要求我们处理两个非空链表,它们各自代表一个逆序存储的非负整数,需要将这两个数相加并以相同形式返回结果链表。
举个例子,如果输入是:
链表1:2 -> 4 -> 3 (表示数字342)
链表2:5 -> 6 -> 4 (表示数字465)
那么输出应该是:7 -> 0 -> 8 (表示数字807,即342+465)
这个题目看似简单,但实际暗藏多个考察点。首先它考察了对链表这种基础数据结构的操作能力,其次需要处理不同长度的链表相加,最重要的是要正确处理进位问题。我在面试候选人和自己刷题过程中发现,至少有30%的候选人会在进位处理上犯错。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数据结构与算法选择
2.1 链表结构分析
题目中给出的链表节点通常定义为:
python复制class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
这种单向链表的特点是每个节点只存储下一个节点的指针,无法直接访问前驱节点。在处理加法问题时,我们需要同时遍历两个链表,从最低位(即链表头)开始相加。这与我们平常做竖式加法的顺序一致,这也是题目设计成逆序存储数字的原因。
2.2 算法思路详解
最优解法的时间复杂度是O(max(m,n)),其中m和n分别是两个链表的长度。空间复杂度也是O(max(m,n)),主要是存储结果链表。算法步骤如下:
- 初始化一个哑节点(dummy node)作为结果链表的头部,这样可以避免处理头节点的特殊情况
- 初始化进位carry为0
- 同时遍历两个链表,直到两个链表都到达末尾且没有进位为止
- 在每一步中:
- 计算当前位置两个节点值的和(如果某个链表已经结束则视为0)
- 加上前一步的进位
- 创建新节点存储和的个位数
- 更新进位为和的十位数
- 返回哑节点的下一个节点作为结果
关键提示:使用哑节点是处理链表问题的常用技巧,可以简化边界条件的处理。很多初学者会忘记最后可能存在的进位,比如5+5=10这种情况。
3. 代码实现与优化
3.1 基础实现版本
以Python为例,基础实现代码如下:
python复制def addTwoNumbers(l1: ListNode, l2: ListNode) -> ListNode:
dummy = ListNode()
current = dummy
carry = 0
while l1 or l2 or carry:
val1 = l1.val if l1 else 0
val2 = l2.val if l2 else 0
total = val1 + val2 + carry
carry = total // 10
current.next = ListNode(total % 10)
current = current.next
l1 = l1.next if l1 else None
l2 = l2.next if l2 else None
return dummy.next
这个版本清晰表达了算法逻辑,但在实际面试中还可以进一步优化。比如可以省略一些条件判断,合并变量赋值等。不过优化前提是不能牺牲代码可读性。
3.2 边界条件处理
这道题有几个关键边界条件需要特别注意:
- 两个链表长度不一致的情况(如123+45)
- 最后有进位的情况(如5+5=10)
- 其中一个链表为空的情况(虽然题目说非空,但防御性编程要考虑)
- 结果为0的情况(如0+0=0)
在测试时,应该至少包含以下测试用例:
- 等长链表无进位(342+465)
- 不等长链表(9999999+9999)
- 有连续进位(99+1)
- 一个链表全零(0+123)
- 最后有进位(5+5)
4. 常见错误与调试技巧
4.1 典型错误模式
根据我在面试和刷题社区中观察到的,常见错误包括:
- 忘记处理最后的进位(约40%的错误属于此类)
- 链表遍历条件不完整(只写了l1 or l2而忘记carry)
- 节点移动时机错误(在计算前就移动了指针)
- 新建节点时搞反了取模和除法操作
4.2 调试技巧
当你的代码出现问题时,可以尝试以下调试方法:
- 可视化链表:在纸上画出链表结构和每一步的变化
- 打印中间变量:在循环中打印val1, val2, total, carry等关键变量
- 使用最小测试用例:先用最简单的case(如1+1)验证基本逻辑
- 边界测试:专门测试进位、不等长等边界情况
我个人的调试习惯是先在纸上手动模拟算法执行过程,这能帮助我发现90%以上的逻辑错误。比如对于输入7->0->8和5->6->4,可以这样模拟:
初始:dummy -> None, carry=0
第一步:7+5=12 → 新节点2,carry=1
第二步:0+6+1=7 → 新节点7,carry=0
第三步:8+4=12 → 新节点2,carry=1
最后:处理carry → 新节点1
这样逐步验证可以确保每个环节都正确无误。
5. 算法优化与变种
5.1 空间优化方案
如果题目要求原地修改链表(不创建新节点),我们可以选择较长的链表作为结果存储。这需要更复杂的指针操作,但能将空间复杂度降为O(1)。不过在实际面试中,除非特别说明,否则使用额外空间创建新链表的方案就足够了。
5.2 相关变种题目
掌握了这道题后,可以尝试解决以下变种问题:
- 数字是正序存储的链表相加(需要先反转链表或使用栈)
- 多个链表相加(可以扩展为k个链表相加)
- 链表表示的浮点数相加(需要处理小数点对齐)
- 链表相减(需要考虑借位而非进位)
这些变种在各大公司的面试题中都曾出现过,比如亚马逊就经常考察正序存储的链表相加问题。
6. 面试实战建议
在面试场景中遇到这道题时,建议采取以下策略:
- 先明确问题:向面试官确认输入输出要求和边界条件
- 举例说明:用具体例子解释你的思路
- 边写边讲:写代码时同步解释关键步骤
- 主动测试:写完代码后主动提出要测试几个案例
- 讨论优化:如果时间允许,讨论可能的优化方向
我在面试候选人时,特别看重他们是否能够清晰表达思路,以及是否考虑到了各种边界情况。一个优秀的候选人会在写代码前就提到最后的进位问题,而不是等到测试时才发
