一、题目回顾
给你两个 非空链表 l1 和 l2,它们表示两个非负整数。
数字的存储方式是:
- 逆序存储
- 每个节点存一位数字
要求:
返回一个新的链表,表示这两个数相加的结果,同样是逆序存储。
示例:
l1: 2 -> 4 -> 3
l2: 5 -> 6 -> 4
表示的数字:
342 + 465 = 807
返回:
7 -> 0 -> 8
二、这道题本质在考什么?
表面看是链表,其实核心是:
如何用链表,模拟人类的“竖式加法”
拆开来看,只做三件事:
- 对应位相加
- 处理进位
- 结果一位一位接到新链表上
关键难点不在算法,而在于:
- 链表怎么一边遍历,一边构建新链表
- 进位如何在不同长度的链表中正确传递
三、整体解题思路
整体策略可以总结为一句话:
同时遍历两个链表,把每一位的和算出来,进位留到下一位
实现上分为四个阶段:
- 同时遍历
l1和l2 - 处理剩余较长的链表
- 最后检查是否还有进位
- 返回真正的结果链表
四、代码整体结构
先看完整代码,再逐块拆解:
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
int total = 0;
int next1 = 0;
ListNode result = new ListNode(0);
ListNode cur = result;
if (l1 == null || l2 == null) {
return result;
}
while (l1 != null && l2 != null) {
total = l1.val + l2.val + next1;
next1 = total / 10;
ListNode r = new ListNode(total % 10);
cur.next = r;
cur = r;
l1 = l1.next;
l2 = l2.next;
}
while (l1 != null) {
total = l1.val + next1;
next1 = total / 10;
ListNode r = new ListNode(total % 10);
cur.next = r;
cur = r;
l1 = l1.next;
}
while (l2 != null) {
total = l2.val + next1;
next1 = total / 10;
ListNode r = new ListNode(total % 10);
cur.next = r;
cur = r;
l2 = l2.next;
}
if (next1 == 1) {
cur.next = new ListNode(1);
}
return result.next;
}
}
五、为什么要用“虚拟头节点”?
ListNode result = new ListNode(0);
ListNode cur = result;
这是链表题中非常经典的技巧。
作用只有一个:
统一“第一个节点”和“后续节点”的处理逻辑
如果不用虚拟头节点:
- 第一次创建节点要特殊判断
- 代码会多出一堆 if/else
现在的做法是:
result永远指向头节点cur永远指向当前尾节点- 所有新节点都用
cur.next = newNode
最后返回时:
return result.next;
跳过这个“占位用”的节点即可。
六、核心:同时遍历两个链表
while (l1 != null && l2 != null) {
total = l1.val + l2.val + next1;
next1 = total / 10;
ListNode r = new ListNode(total % 10);
cur.next = r;
cur = r;
l1 = l1.next;
l2 = l2.next;
}
这段代码完整地模拟了「竖式加法的一列」。
逐步理解:
- 当前位的和 = 两个节点的值 + 上一位进位
total / 10是新的进位total % 10是当前位的结果- 把结果节点接到结果链表末尾
- 两个指针同时向后移动
这一段处理的是:
两个链表都还有数字的情况
七、为什么要分开处理“剩余链表”?
链表长度可能不同,例如:
l1: 9 -> 9 -> 9
l2: 1
当 l2 先结束后:
- 仍然需要把
l1剩余的节点加上进位 - 不能直接拼接
所以需要两个独立的 while:
while (l1 != null) { ... }
while (l2 != null) { ... }
逻辑完全一致:
- 当前值 + 进位
- 更新进位
- 生成新节点
八、最后一步:别忘了最高位进位
if (next1 == 1) {
cur.next = new ListNode(1);
}
这是很多人第一次写这道题时最容易漏掉的一步。
典型场景:
l1: 9 -> 9
l2: 1
结果:
0 -> 0 -> 1
如果没有这一步,最高位的 1 会直接丢失。
九、复杂度分析
-
时间复杂度:
O(max(m, n))- m、n 为两个链表的长度
-
空间复杂度:
O(max(m, n))- 结果链表占用的空间
十、总结
这道题的关键不在链表本身,而在于:
把人类的“按位加法”过程,精确地翻译成代码
几个非常值得反复体会的点:
- 用虚拟头节点简化链表构建
- 把进位当成一个“跨轮次状态”
- 不同长度链表要分阶段处理
- 最后一定要检查是否还有进位
理解透这一题后,你会发现:
- 很多链表题,本质都是“遍历 + 状态传递”
- 思路清楚了,代码自然就顺了