一、题目理解
题目描述
给你两个非空 的链表,表示两个非负的整数。它们每位数字都是按照逆序 的方式存储的,并且每个节点只能存储一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例
输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807,逆序存储后结果为 [7,0,8]。
二、解题思路
这道题的核心是模拟手工加法的过程,关键点如下:
- 两个链表逆序存储数字,正好符合"从个位开始相加"的加法逻辑;
- 相加时需要处理进位(进位值只能是 0 或 1,因为两个个位数相加最大是 9+9=18,进位1);
- 两个链表长度可能不同,需要处理"短链表遍历完但长链表还有剩余"的情况;
- 最后如果还有进位(比如 999 + 1 = 1000),需要额外新增一个节点存储进位。
三、Java代码实现
首先定义题目中的链表节点类(力扣会默认提供,无需自己写),然后实现核心逻辑:
java
// 链表节点定义(力扣题目中已给出)
class ListNode {
int val;
ListNode next;
ListNode() {}
ListNode(int val) { this.val = val; }
ListNode(int val, ListNode next) { this.val = val; this.next = next; }
}
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
// 虚拟头节点(简化链表操作,避免处理头节点为空的情况)
ListNode dummyHead = new ListNode(0);
// 遍历指针,用于构建结果链表
ListNode curr = dummyHead;
// 进位值,初始为0
int carry = 0;
// 循环条件:l1未遍历完 或 l2未遍历完 或 还有进位
while (l1 != null || l2 != null || carry != 0) {
// 取出当前节点的值,若节点为空则取0
int val1 = (l1 != null) ? l1.val : 0;
int val2 = (l2 != null) ? l2.val : 0;
// 计算当前位的和(包含进位)
int sum = val1 + val2 + carry;
// 当前位的结果:和对10取余
int currentVal = sum % 10;
// 更新进位:和除以10取整(只能是0或1)
carry = sum / 10;
// 创建新节点,加入结果链表
curr.next = new ListNode(currentVal);
// 指针后移
curr = curr.next;
// 原链表指针后移(非空时)
if (l1 != null) l1 = l1.next;
if (l2 != null) l2 = l2.next;
}
// 虚拟头节点的下一个节点才是结果链表的头
return dummyHead.next;
}
}
四、代码关键解析
-
虚拟头节点(dummyHead)
- 为什么用?如果直接用真实头节点,需要额外判断"结果链表是否为空",虚拟头节点可以统一处理所有节点的添加逻辑,最后返回
dummyHead.next即可。 - 比如示例中,先创建
dummyHead(0),然后依次添加 7、0、8,最终返回dummyHead.next就是 [7,0,8]。
- 为什么用?如果直接用真实头节点,需要额外判断"结果链表是否为空",虚拟头节点可以统一处理所有节点的添加逻辑,最后返回
-
循环条件
l1 != null || l2 != null || carry != 0:覆盖三种情况:- l1还有节点未遍历;
- l2还有节点未遍历;
- 两个链表都遍历完,但最后一位相加有进位(如 999 + 1 = 1000)。
-
数值处理
val1 = l1 != null ? l1.val : 0:短链表遍历完后,后续位用 0 补充(比如 l1=[2,4], l2=[5,6,4],则 l1 的第三位视为 0);sum = val1 + val2 + carry:必须加上上一位的进位;currentVal = sum % 10:取当前位的结果(如 4+6=10,当前位是 0);carry = sum / 10:更新进位(如 4+6=10,进位是 1)。
-
指针移动
- 结果链表的指针
curr每次后移,指向新创建的节点; - 原链表的指针
l1/l2只有非空时才后移,避免空指针异常。
- 结果链表的指针
五、测试示例
以 l1 = [2,4,3], l2 = [5,6,4] 为例,执行过程:
| 步骤 | val1 | val2 | carry | sum | currentVal | 结果链表(curr指向) |
|---|---|---|---|---|---|---|
| 1 | 2 | 5 | 0 | 7 | 7 | dummyHead -> 7 |
| 2 | 4 | 6 | 0 | 10 | 0 | dummyHead -> 7 -> 0 |
| 3 | 3 | 4 | 1 | 8 | 8 | dummyHead -> 7 -> 0 -> 8 |
| 4 | 0 | 0 | 0 | 0 | - | 循环结束 |
最终返回 dummyHead.next 即 [7,0,8],符合预期。 |
总结
- 核心逻辑:模拟手工加法,从个位(链表头)开始相加,处理进位和链表长度不一致的问题;
- 关键技巧:使用虚拟头节点简化链表操作,循环条件覆盖"链表未遍历完"和"最后有进位"两种场景;
- 边界处理:短链表补 0、最后进位新增节点,避免空指针异常。
这道题是链表的入门经典题,掌握后能理解链表遍历、虚拟头节点、进位处理等核心考点,后续可以尝试优化空间(比如直接在原链表上修改),但上述解法是最易理解、面试中最推荐的写法。