1. LeetCode第二题解析:两数相加算法精讲
第一次看到LeetCode第二题时,很多人会觉得这不就是个简单的加法题吗?但真正动手实现时,才会发现链表操作和进位处理的那些坑。作为刷过300+题的过来人,我想分享下这道经典链表题的正确打开方式。
这道题要求我们用链表表示两个非负整数,并返回它们和的链表表示。看似简单,实则考察了链表遍历、指针操作和边界条件处理等核心编程能力。我在面试中被问过三次这道题,每次都能从面试官那里学到新的优化思路。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 题目分析与算法设计
2.1 问题重述
给定两个非空链表,分别表示两个非负整数。数字以逆序存储(即个位在链表头部),每个节点存储一位数字。要求将这两个数相加,并以相同形式返回结果链表。
示例:
输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
解释:342 + 465 = 807
2.2 核心难点解析
- 链表长度不一致:两个数字位数可能不同,需要处理长短链表的情况
- 进位处理:相加可能产生进位,最高位相加可能产生新的进位位
- 指针操作:需要同时遍历两个链表并构建结果链表
2.3 算法选择
最直观的解法是模拟竖式加法:
- 初始化哑节点(dummy node)简化链表操作
- 同时遍历两个链表,逐位相加并计算进位
- 处理最后可能的进位
- 返回哑节点的下一个节点
时间复杂度O(max(m,n)),空间复杂度O(max(m,n)),其中m,n为两个链表的长度。
3. 代码实现与逐行解析
3.1 Python实现
python复制class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
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
digit = total % 10
# 创建新节点
current.next = ListNode(digit)
current = current.next
# 移动指针
l1 = l1.next if l1 else None
l2 = l2.next if l2 else None
return dummy.next
3.2 关键代码解析
- 哑节点技巧:使用dummy节点可以避免处理头节点的特殊情况
- 循环条件:
while l1 or l2 or carry确保处理完所有数字和最后的进位 - 空值处理:当链表较短时用0补位,避免空指针异常
- 进位计算:
carry = total // 10计算进位,digit = total % 10得到当前位数字
注意:在面试中,一定要先解释清楚算法思路再写代码。我曾因为直接写代码没解释清楚而被要求重写。
4. 边界条件与测试用例
4.1 必须考虑的边界情况
- 两个链表长度不同
- (1->2->3) + (4->5)
- 最后有进位
- (5) + (5) = (0->1)
- 包含0的情况
- (0) + (0) = (0)
- 长链表(测试性能)
- (1->1->...->1) + (9->9->...->9)
4.2 单元测试示例
python复制import unittest
class TestAddTwoNumbers(unittest.TestCase):
def test_equal_length(self):
l1 = ListNode(2, ListNode(4, ListNode(3)))
l2 = ListNode(5, ListNode(6, ListNode(4)))
result = addTwoNumbers(l1, l2)
self.assertEqual(result.val, 7)
self.assertEqual(result.next.val, 0)
self.assertEqual(result.next.next.val, 8)
def test_different_length(self):
l1 = ListNode(9, ListNode(9))
l2 = ListNode(1)
result = addTwoNumbers(l1, l2)
self.assertEqual(result.val, 0)
self.assertEqual(result.next.val, 0)
self.assertEqual(result.next.next.val, 1)
5. 常见错误与优化技巧
5.1 新手常见错误
-
忘记处理最后进位:
python复制# 错误示例 while l1 or l2: # 缺少carry判断 ... -
指针操作错误:
python复制# 错误示例 current = ListNode(digit) # 没有连接链表 current = current.next -
空指针异常:
python复制# 错误示例 val1 = l1.val # 当l1为None时会报错
5.2 优化技巧
- 空间优化:可以复用较长的链表节点来节省空间
- 提前终止:当较短的链表遍历完且无进位时,可以直接链接剩余链表
- 并行计算:对于超长链表可以考虑并行计算不同区段
我在一次面试中因为提到可以并行计算长链表获得了加分。虽然面试不需要实现,但展示这种思维很有帮助。
6. 复杂度分析与变种问题
6.1 时间复杂度分析
- 最佳情况:O(min(m,n))(无进位且链表长度差大)
- 最坏情况:O(max(m,n))(需要处理所有节点和进位)
6.2 空间复杂度
- 结果链表需要O(max(m,n))空间
- 如果允许修改原链表,可以优化到O(1)空间
6.3 相关问题扩展
-
数字正序存储:如果数字是正序存储的(即头节点是最高位),如何解决?
- 解法:可以使用栈反转链表,或递归实现
-
多个数相加:如何计算多个链表表示的数字之和?
- 解法:可以扩展当前算法,维护一个进位变量同时遍历多个链表
-
浮点数相加:如果链表表示浮点数(包含小数点节点),如何实现?
- 解法:先对齐小数点位置,再分段计算
7. 面试实战建议
- 先理清思路:在白板上画出链表相加的示意图,明确每一步操作
- 边写边讲:解释每个变量的作用(如carry的用途)
- 主动测试:写完代码后主动用测试用例验证
- 讨论优化:即使时间不够实现优化,也可以口头讨论可能的优化方向
我在面试中遇到过要求手写测试用例的情况,所以建议准备几个典型测试用例。另外,有些面试官会要求处理数字正序存储的变种,可以提前思考这类问题的解法。
