📌 题目链接:2. 两数相加 - 力扣(LeetCode)
🔍 难度:中等 | 🏷️ 标签:链表、数学、模拟
⏱️ 目标时间复杂度:O(max(m,n))
💾 空间复杂度:O(1)(不计返回链表空间)
✅ 本期我们深入剖析 LeetCode 热题 100 中的第 28 题:2.两数相加。这道题看似简单,实则考察了对链表结构的熟练掌握、数学进位逻辑的精准模拟,以及边界情况的严谨处理能力。是面试中常考的经典链表题之一!🔥
🎯 题目分析
给你两个 非空链表,表示两个非负整数,数字按 逆序 存储在链表中(即个位在前,高位在后)。例如:
l1 = [2,4,3]表示数字342l2 = [5,6,4]表示数字465
要求你将这两个数相加,并以相同形式返回一个新链表。
📌 关键点:
- 数字是逆序存储 → 可直接从头开始逐位相加
- 每个节点只能存一位数字(0~9)
- 可能存在进位 → 需要维护
carry - 两个链表长度可能不同 → 短链表后续视为 0
- 最后一位若仍有进位,需额外添加节点
🎯 输出示例:
[7,0,8] 对应 807,因为 342 + 465 = 807
🔍 核心算法及代码讲解
🧮 核心思想:模拟竖式加法
这是典型的「模拟」类题目,核心在于模仿我们小学学过的 竖式加法过程:
3 4 2
+ 4 6 5
---------
8 0 7
但注意:由于链表是逆序存储,所以我们可以直接从左到右逐位相加!
✅ 关键步骤分解:
| 步骤 | 描述 |
|---|---|
| 1️⃣ | 初始化 head 和 tail 指针用于构建结果链表 |
| 2️⃣ | 使用 carry 记录进位(初始为 0) |
| 3️⃣ | 同时遍历 l1 和 l2,直到两者都为空 |
| 4️⃣ | 当前位和 = n1 + n2 + carry |
| 5️⃣ | 新节点值 = (sum % 10),进位 = sum / 10 |
| 6️⃣ | 构建新节点并连接到结果链表尾部 |
| 7️⃣ | 若最后还有进位,追加一个值为 carry 的节点 |
💡 进位机制详解:
int sum = n1 + n2 + carry;
int digit = sum % 10; // 当前位数字
int new_carry = sum / 10; // 新的进位(0 或 1)
⚠️ 注意:由于每位最多是 9,两个数加起来最大是
9+9+1=19,所以进位最多为 1,不会超过 1。
🧩 解题思路(分步拆解)
-
初始化变量
head: 结果链表头指针(首次创建时赋值)tail: 当前尾节点(用于不断追加新节点)carry: 进位值,初始为 0
-
循环遍历两个链表
- 使用
while (l1 || l2)确保即使其中一个链表结束,仍继续处理另一个 - 安全取值:
n1 = l1 ? l1->val : 0,避免空指针访问
- 使用
-
计算当前位和
sum = n1 + n2 + carrydigit = sum % 10carry = sum / 10
-
构造新节点
- 如果
head为空 → 创建第一个节点 - 否则 →
tail->next = new ListNode(digit)并更新tail
- 如果
-
移动指针
- 若
l1不为空,则l1 = l1->next - 若
l2不为空,则l2 = l2->next
- 若
-
处理最终进位
- 若
carry > 0,说明最高位有进位(如999 + 1 = 1000),需追加一个节点
- 若
-
返回结果
- 返回
head即可
- 返回
📊 算法分析
| 项目 | 分析 |
|---|---|
| ✅ 时间复杂度 | O(max(m,n)) 其中 m、n 是两个链表的长度 我们需要遍历最长的那个链表一次 |
| ✅ 空间复杂度 | O(max(m,n)) 结果链表的空间与输入长度有关 ⚠️ 但通常说空间复杂度为 O(1),因为不计入返回值空间(面试中常见说法) |
| ✅ 是否原地操作 | ❌ 不是原地修改,创建了新的链表 |
| ✅ 是否递归 | ❌ 迭代实现,更高效稳定 |
| ✅ 面试考点 | 链表操作、边界处理、进位逻辑、空指针安全、构造新链表 |
💡 面试加分点:
- 能清晰说出“为什么用
while(l1 || l2)”?- 能解释“为什么进位最多为 1”?
- 能写出边界测试用例(如
[9,9,9] + [1])?
💻 代码(保留原始模板,完整注释)
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
// 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 *head = nullptr, *tail = nullptr;
// 进位变量,初始为 0
int carry = 0;
// 循环条件:只要 l1 或 l2 还有节点,就继续处理
while (l1 || l2) {
// 安全获取当前位的值(如果链表已结束,值为 0)
int n1 = l1 ? l1->val : 0;
int n2 = l2 ? l2->val : 0;
// 计算当前位总和(包括上一位的进位)
int sum = n1 + n2 + carry;
// 当前位数字(模 10)
int digit = sum % 10;
// 更新进位(除以 10)
carry = sum / 10;
// 构造新节点
if (!head) {
// 第一个节点:初始化 head 和 tail
head = tail = new ListNode(digit);
} else {
// 非首个节点:追加到尾部
tail->next = new ListNode(digit);
tail = tail->next;
}
// 移动指针到下一个节点
if (l1) {
l1 = l1->next;
}
if (l2) {
l2 = l2->next;
}
}
// 处理最后的进位(如 999 + 1 = 1000)
if (carry > 0) {
tail->next = new ListNode(carry);
}
return head;
}
};
// 测试
signed main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
// 示例 1: [2,4,3] + [5,6,4] = [7,0,8]
ListNode* l1 = new ListNode(2);
l1->next = new ListNode(4);
l1->next->next = new ListNode(3);
ListNode* l2 = new ListNode(5);
l2->next = new ListNode(6);
l2->next->next = new ListNode(4);
Solution sol;
ListNode* result = sol.addTwoNumbers(l1, l2);
// 打印结果
while (result) {
cout << result->val << " ";
result = result->next;
}
cout << endl;
return 0;
}
📌 运行输出:
7 0 8
🧪 测试用例验证
| 输入 | 输出 | 说明 |
|---|---|---|
[2,4,3], [5,6,4] | [7,0,8] | 基础案例 |
[0], [0] | [0] | 边界情况 |
[9,9,9,9,9,9,9], [9,9,9,9] | [8,9,9,9,0,0,0,1] | 长链表 + 进位 |
[1], [9,9] | [0,0,1] | 进位传播 |
✅ 所有测试通过!
🌟 本期完结,下期见!🔥
👉 点赞收藏加关注,新文更新不迷路。关注专栏【算法】LeetCode Hot100刷题日记,持续为你拆解每一道热题的底层逻辑与面试技巧!
💬 欢迎留言交流你的解法或疑问!一起进步,冲向 Offer!💪
📣 下一期预告:LeetCode 热题 100 第29题 —— 19.删除链表的倒数第 N 个结点 🚫
🔹 题目:给定一个链表,删除其倒数第
N个节点(假设链表至少有N个节点)。🔹 核心思路:使用双指针(快慢指针),先让快指针走
N步,然后快慢指针同时前进,当快指针到达末尾时,慢指针指向的就是目标节点的前一个节点。🔹 考点:链表、双指针、边界处理、虚拟头节点技巧。
🔹 难度:中等,是链表操作中的经典题型,常出现在大厂笔试中。
💡 提示:不要用两次遍历!要用一次遍历完成,且注意
N可能等于链表长度的情况!
📌 记住:当你在刷题时,不要只看答案,要像写这篇文章一样,深入思考每一步背后的原理、优化空间和面试价值。这才是真正提升算法能力的方式!
✅ 本篇总结:
- 掌握了链表模拟加法的核心逻辑
- 理解了进位机制与边界处理
- 学会了如何安全遍历两个长度不同的链表
- 代码结构清晰,适合面试复述与手写
📌 推荐练习:
- 2. 两数相加 II(正序存储)
- 445. 两数相加 II(栈实现)
- 7. 整数反转(相关数学思维)
🔁 坚持每日一题,算法不再难!
🚀 下一站:删除链表倒数第 N 个节点,我们不见不散!