1. 题目描述
给你两个非空链表,表示两个非负整数。每位数字都是按照逆序存储的,并且每个节点只能存储一位数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例
输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807
输入:l1 = [0], l2 = [0]
输出:[0]
输入:l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9]
输出:[8,9,9,9,0,0,0,1]
2. 核心思想
关键思想:链表模拟竖式加法
两个数字按逆序存储,从低位到高位逐位相加,满十进一。
3 → 4 → 2 (数字 342)
+ 4 → 6 → 5 (数字 465)
────────────
7 → 0 → 8 (数字 807)
核心要点:
- 逐节点遍历:
l1.val + l2.val + carry - 进位
carry = sum / 10 - 当前位
sum % 10 - 最后还要检查进位(如 999 + 1 = 1000)
3. 多种方法解决
方法一:虚拟头节点(推荐)✅
cpp
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode* dummy = new ListNode(0); // 虚拟头节点
ListNode* head = dummy; // 移动指针
int carry = 0; // 进位
while (l1 || l2) {
int x = l1 ? l1->val : 0;
int y = l2 ? l2->val : 0;
int sum = x + y + carry;
carry = sum / 10; // 更新进位
head->next = new ListNode(sum % 10); // 创建新节点
head = head->next; // 移动指针
if (l1) l1 = l1->next;
if (l2) l2 = l2->next;
}
// 处理最后的进位(如 999 + 1 = 1000)
if (carry) {
head->next = new ListNode(carry);
}
return dummy->next; // 跳过虚拟头节点
}
};
复杂度: 时间 O(max(m,n)),空间 O(max(m,n))
方法二:递归写法
cpp
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
return addTwoNumbersHelper(l1, l2, 0);
}
private:
ListNode* addTwoNumbersHelper(ListNode* l1, ListNode* l2, int carry) {
// 递归终止条件:两个链表都走完且没有进位
if (!l1 && !l2 && carry == 0) {
return nullptr;
}
int x = l1 ? l1->val : 0;
int y = l2 ? l2->val : 0;
int sum = x + y + carry;
ListNode* node = new ListNode(sum % 10);
node->next = addTwoNumbersHelper(
l1 ? l1->next : nullptr,
l2 ? l2->next : nullptr,
sum / 10
);
return node;
}
};
复杂度: 时间 O(max(m,n)),空间 O(max(m,n))(递归栈)
方法三:先反转链表再做(不推荐)
- 先把两个链表反转
- 按正常顺序做加法
- 再把结果反转
cpp
// 步骤1:反转链表
ListNode* reverseList(ListNode* head) {
ListNode* prev = nullptr;
ListNode* curr = head;
while (curr) {
ListNode* next = curr->next;
curr->next = prev;
prev = curr;
curr = next;
}
return prev;
}
// 步骤2:普通加法
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
l1 = reverseList(l1);
l2 = reverseList(l2);
// ... 普通加法逻辑 ...
return reverseList(result);
}
复杂度: 时间 O(m+n),空间 O(1),但代码复杂,不推荐 ❌
4. 图解过程
示例:l1 = [2,4,3], l2 = [5,6,4]
初始状态:
dummy → nullptr
carry = 0
┌─────────────────────────────────────────┐
│ 第1位(个位):2 + 5 + 0 = 7 │
│ │
│ l1: 2 → 4 → 3 │
│ l2: 5 → 6 → 4 │
│ sum = 7, carry = 0 │
│ │
│ dummy → [7] │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│ 第2位(十位):4 + 6 + 0 = 10 │
│ │
│ sum = 10, carry = 1 │
│ 当前位:0 │
│ │
│ dummy → [7] → [0] │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│ 第3位(百位):3 + 4 + 1 = 8 │
│ │
│ sum = 8, carry = 0 │
│ │
│ dummy → [7] → [0] → [8] │
└─────────────────────────────────────────┘
遍历结束,carry = 0,无需额外节点
结果:7 → 0 → 8 ✓
边界情况:l1 = [9,9,9], l2 = [1]
第1位:9 + 1 + 0 = 10 → 0, carry=1
第2位:9 + 0 + 1 = 10 → 0, carry=1
第3位:9 + 0 + 1 = 10 → 0, carry=1
遍历完:l1 结束,l2 结束,但 carry=1
额外节点:1
结果:0 → 0 → 0 → 1 ✓ (1000)
5. 方法优缺点比较
| 方法 | 时间 | 空间 | 优点 | 缺点 |
|---|---|---|---|---|
| 虚拟头节点 | O(max(m,n)) | O(max(m,n)) | ✅ 代码清晰,逻辑直接 | 需要 new 节点 |
| 递归 | O(max(m,n)) | O(max(m,n)) | ✅ 代码简洁 | ❌ 递归栈占用,可能栈溢出 |
| 先反转链表 | O(m+n) | O(1) | ✅ 空间最优 | ❌ 代码复杂,多遍历2次,不推荐 |
推荐方法
方法一(虚拟头节点) 是面试和竞赛中最常用的写法,理由:
- 逻辑直观,不易出错
- 无递归栈溢出风险
- 代码可读性高
6. 完整测试代码
cpp
#include <iostream>
#include <vector>
using namespace std;
struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(nullptr) {}
};
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode* dummy = new ListNode(0);
ListNode* head = dummy;
int carry = 0;
while (l1 || l2) {
int x = l1 ? l1->val : 0;
int y = l2 ? l2->val : 0;
int sum = x + y + carry;
carry = sum / 10;
head->next = new ListNode(sum % 10);
head = head->next;
if (l1) l1 = l1->next;
if (l2) l2 = l2->next;
}
if (carry) {
head->next = new ListNode(carry);
}
return dummy->next;
}
};
// 辅助函数:链表转向量
vector<int> toVector(ListNode* head) {
vector<int> result;
while (head) {
result.push_back(head->val);
head = head->next;
}
return result;
}
// 辅助函数:向量转链表
ListNode* toList(vector<int> vals) {
ListNode* dummy = new ListNode(0);
ListNode* head = dummy;
for (int v : vals) {
head->next = new ListNode(v);
head = head->next;
}
return dummy->next;
}
int main() {
Solution sol;
// 测试1:342 + 465 = 807
ListNode* l1 = toList({2, 4, 3});
ListNode* l2 = toList({5, 6, 4});
vector<int> r1 = toVector(sol.addTwoNumbers(l1, l2));
cout << "342 + 465 = ";
for (int v : r1) cout << v;
cout << " (expected: 708)" << endl;
// 测试2:0 + 0 = 0
vector<int> r2 = toVector(sol.addTwoNumbers(toList({0}), toList({0})));
cout << "0 + 0 = " << r2[0] << " (expected: 0)" << endl;
// 测试3:9999999 + 9999 = 10009998
ListNode* l5 = toList({9,9,9,9,9,9,9});
ListNode* l6 = toList({9,9,9,9});
vector<int> r3 = toVector(sol.addTwoNumbers(l5, l6));
cout << "9999999 + 9999 = ";
for (int v : r3) cout << v;
cout << " (expected: 10009998)" << endl;
return 0;
}
输出:
342 + 465 = 708 (expected: 708)
0 + 0 = 0 (expected: 0)
9999999 + 9999 = 10009998 (expected: 10009998)
8. 总结
| 要点 | 说明 |
|---|---|
| 核心思想 | 链表模拟竖式加法 |
| 虚拟头节点 | 简化头节点处理 |
| 进位处理 | 遍历结束后还要检查 carry |
| 边界条件 | 链表长度不同、空链表 |
| 推荐写法 | 虚拟头节点(方法一) |