
本文详解 leetcode「两数相加」题目的标准解法,重点剖析因未初始化 `next` 节点导致的编译期错误(如 `cannot assign field "val" because "
你在代码中遇到的错误:
temp.next.val = rem; // ❌ 编译报错:Cannot assign field "val" because ".next" is null
根本原因在于:temp 当前指向一个刚创建的 ListNode(例如 dummy = new ListNode(0)),其 next 字段默认为 null。而你直接对 temp.next.val 赋值,相当于尝试访问 null.val —— 即使尚未运行到该行,现代 Java 编译器(尤其是启用了严格空检查的环境,如某些 IDE 或 Lombok/Checker Framework 集成)也会在编译期提前捕获这一必然空指针风险,并拒绝编译。
更严重的是,你的原始思路存在三重逻辑缺陷:
- ✅ 错误地对同一链表 l1 调用了两次 reverse1(l1) 和 reverse2(l1)(应为 reverse2(l2));
- ❌ 用反转 + 转整数再相加的方式极易溢出(例3中 7位9+4位9远超 int/long 范围);
- ❌ 构造结果链表时未创建新节点,而是试图向未初始化的 next 写值,违反链表基本构造规则。
✅ 正确解法:不反转、不转数字、不依赖大整数,仅用一次遍历模拟手工加法(带进位),时间 O(max(m,n)),空间 O(1)(不含输出链表)。
✅ 标准安全实现(推荐)
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode dummy = new ListNode(0); // 哨兵头节点,简化边界处理
ListNode curr = dummy;
int carry = 0;
while (l1 != null || l2 != null || carry != 0) {
int sum = carry;
if (l1 != null) {
sum += l1.val;
l1 = l1.next;
}
if (l2 != null) {
sum += l2.val;
l2 = l2.next;
}
carry = sum / 10;
int digit = sum % 10;
curr.next = new ListNode(digit); // ✅ 关键:先创建新节点,再链接!
curr = curr.next;
}
return dummy.next; // 跳过哨兵
}
}? 关键要点解析
- curr.next = new ListNode(digit) 是唯一安全写法:每次循环都显式 new 一个节点并赋给 curr.next,确保 curr.next 永不为 null,后续 curr = curr.next 才能安全推进。
- 无需反转:题目明确“数字以逆序存储”,即 l1 = [2,4,3] 表示 342,个位在前——这恰好匹配加法从低位开始的自然顺序,直接遍历即可。
- 进位处理统一:carry 初始为 0,循环条件包含 || carry != 0,确保最高位进位(如 999 + 1 = 1000)也能被正确生成。
- 空节点兼容:通过 if (l1 != null) 等判断,天然支持两链表长度不等的情况(如例3)。
⚠️ 注意事项
- 不要尝试复用输入节点或修改原链表结构(除非题目允许且明确要求空间优化);
- 避免 int/long 转换:链表长度可能达 100,数值远超 2^63,整数转换必溢出;
- ListNode 构造必须显式调用:new ListNode(val),不能假设 next 已就绪;
- 若使用 Kotlin 或启用了 @Nullable 注解的 Java 项目,此错误会更早暴露——这是编译器在帮你预防运行时崩溃。
掌握这种“边遍历边构造”的模式,是解决链表类问题的核心范式。它简洁、健壮、符合题目约束,也是面试官期望看到的标准答案。










