力扣hot100-2.两数相加-链表模拟与进位处理详解

2. 两数相加:链表模拟与进位处理详解

题目链接:2. 两数相加

题解类型:

text 复制代码
链表模拟 / 逐位相加 / 进位处理

这题的核心不难:像手算加法一样,从个位开始逐位相加。

真正容易出错的地方是边界处理:

text 复制代码
两条链表长度不同怎么办?
最后还有进位怎么办?
当前位和下一轮进位分别怎么计算?
结果链表的第一个节点怎么连接?

算法思路

题目使用倒序链表存储一个非负整数。

例如:

text 复制代码
2 -> 4 -> 3

表示的数字是:

text 复制代码
342

因为链表头部存的是个位:

text 复制代码
个位 2 -> 十位 4 -> 百位 3

所以:

text 复制代码
l1:2 -> 4 -> 3,表示 342
l2:5 -> 6 -> 4,表示 465

342 + 465 = 807

结果也要倒序存储:

text 复制代码
7 -> 0 -> 8,表示 807

倒序存储让链表头部正好是个位,因此可以直接从 head 开始模拟手算加法。


1. 每一位到底算什么

假设当前轮:

text 复制代码
l1 当前位:x
l2 当前位:y
上一位进位:carry

当前轮的总和是:

java 复制代码
sum = x + y + carry;

其中:

text 复制代码
sum % 10:当前结果节点保存的个位。
sum / 10:交给下一轮的进位。

例如:

text 复制代码
9 + 8 + 0 = 17

因此:

text 复制代码
当前结果位:17 % 10 = 7
下一轮进位:17 / 10 = 1

当前结果链表接入 7,并把 1 留给下一轮。

在本题中每一位最大是:

text 复制代码
9 + 9 + 1 = 19

所以 carry 只可能是 01


2. 完整推演:342 + 465

text 复制代码
l1:2 -> 4 -> 3
l2:5 -> 6 -> 4

初始:

text 复制代码
carry = 0

第 1 轮:个位

text 复制代码
2 + 5 + 0 = 7
text 复制代码
结果位:7 % 10 = 7
进位:  7 / 10 = 0

结果链表:

text 复制代码
7

第 2 轮:十位

text 复制代码
4 + 6 + 0 = 10
text 复制代码
结果位:10 % 10 = 0
进位:  10 / 10 = 1

结果链表:

text 复制代码
7 -> 0

第 3 轮:百位

text 复制代码
3 + 4 + 1 = 8
text 复制代码
结果位:8 % 10 = 8
进位:  8 / 10 = 0

结果链表:

text 复制代码
7 -> 0 -> 8

两条链表都走完,且 carry == 0,结束。


3. 易错点一:两条链表长度不同,空位按 0 处理

例如:

text 复制代码
l1:2 -> 4 -> 9,表示 942
l2:5 -> 6,表示 65

百位时,l2 已经没有节点,但不是停止计算,而是应该把缺失的一位当作 0

text 复制代码
9 + 0 + carry

所以每轮取值时写成:

java 复制代码
int x = (l1 == null) ? 0 : l1.val;
int y = (l2 == null) ? 0 : l2.val;

含义是:

text 复制代码
对应链表还有节点:取当前节点值。
对应链表已结束:当前位补 0。

不能因为其中一条链表结束,就直接停止循环;另一条链表的高位仍然需要放进结果。


4. 易错点二:两个链表结束后,进位可能还存在

例如:

text 复制代码
l1:9 -> 9,表示 99
l2:1,表示 1

99 + 1 = 100

推演:

text 复制代码
第 1 轮:9 + 1 + 0 = 10 -> 当前位 0,carry = 1
第 2 轮:9 + 0 + 1 = 10 -> 当前位 0,carry = 1

此时:

text 复制代码
l1 == null
l2 == null
carry == 1

最高位的 1 仍必须创建为结果节点:

text 复制代码
0 -> 0 -> 1

因此循环条件必须写为:

java 复制代码
while (l1 != null || l2 != null || carry != 0)

三个条件的含义是:

text 复制代码
l1 != null:l1 还有位没有处理。
l2 != null:l2 还有位没有处理。
carry != 0:还有最高位进位没有写入结果。

只要其中任何一个成立,就必须继续。


5. 易错点三:% 10/ 10 不能写反

假设:

text 复制代码
sum = 17

正确写法:

java 复制代码
int digit = sum % 10; // 7,当前节点保存的个位
carry = sum / 10;     // 1,下一轮使用的进位

原因是十进制数 17 可以拆成:

text 复制代码
17 = 1 * 10 + 7

其中:

text 复制代码
7 留在当前位。
1 移交给更高一位。

因此不能写成:

java 复制代码
tail.next = new ListNode(sum / 10);
carry = sum % 10;

那样会把进位与当前位完全弄反。


6. 为什么需要 dummytail

每一轮相加都会产生一个新的结果节点,因此需要不断向结果链表末尾追加节点。

先创建:

java 复制代码
ListNode dummy = new ListNode(-1);
ListNode tail = dummy;

初始结构:

text 复制代码
dummy -> null
  ^
tail

每算出一位:

java 复制代码
tail.next = new ListNode(sum % 10);
tail = tail.next;

tail 始终指向结果链表当前最后一个节点。

dummy 是虚拟头节点,不属于答案;真正的结果从:

java 复制代码
dummy.next

开始。

使用 dummy 的价值是:第一个结果节点和之后的结果节点可以使用同一套连接代码,不需要专门处理"结果链表还没有头节点"的情况。


7. Java 代码完整注释

java 复制代码
class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        // dummy 是结果链表的虚拟头节点。
        // 它只用于统一连接操作,不属于最终结果。
        ListNode dummy = new ListNode(-1);

        // tail 始终指向结果链表当前最后一个节点。
        ListNode tail = dummy;

        // carry 保存上一位相加产生、需要加到当前位的进位。
        int carry = 0;

        // 只要任一链表还有节点,或还有最终进位,就继续计算。
        while (l1 != null || l2 != null || carry != 0) {
            // l1 已走完时,当前位按 0 处理。
            int x = (l1 == null) ? 0 : l1.val;

            // l2 已走完时,当前位按 0 处理。
            int y = (l2 == null) ? 0 : l2.val;

            // 当前位总和 = 两个当前位 + 低一位传来的进位。
            int sum = x + y + carry;

            // 当前结果节点保存个位。
            tail.next = new ListNode(sum % 10);

            // tail 移动到刚创建的结果节点。
            tail = tail.next;

            // 十位成为下一轮的进位。
            carry = sum / 10;

            // 还存在节点的链表向后移动一位。
            if (l1 != null) {
                l1 = l1.next;
            }

            if (l2 != null) {
                l2 = l2.next;
            }
        }

        // dummy 是辅助节点,真正的结果头节点是 dummy.next。
        return dummy.next;
    }
}

8. 易错点清单

易错点 错误后果 正确处理
一条链表结束就停止 另一条链表的高位丢失 空位按 0 处理
循环不判断 carry 最终最高位进位丢失 `while (l1 != null
sum % 10sum / 10 写反 当前结果位和进位错误 % 10 写当前位,/ 10carry
未判断 l1l2 是否为 null 就访问 .val 空指针异常 三元表达式补 0
返回 dummy 答案多出虚拟节点 -1 返回 dummy.next
不移动 tail 后续节点不能正确追加 每轮创建节点后 tail = tail.next

写完代码后,至少用下面三类情况手动检查一次:

text 复制代码
普通情况:342 + 465 = 807
长度不同:942 + 65 = 1007
最终进位:99 + 1 = 100

对应链表结果应分别是:

text 复制代码
7 -> 0 -> 8
7 -> 0 -> 0 -> 1
0 -> 0 -> 1

9. 复杂度

假设两条链表长度分别为 mn

时间复杂度:

text 复制代码
O(max(m, n))

每一位只处理一次,最多额外处理一次最终进位。

额外空间复杂度:

text 复制代码
O(1)

这里不把题目要求返回的结果链表算入额外空间。除结果链表外,只使用了 dummytailcarry 等常数个变量。

一句话记忆:

text 复制代码
当前位 = x + y + carry;结果节点存 sum % 10;下一轮进位是 sum / 10;空位补 0,最终进位也不能漏。
相关推荐
午彦琳41 分钟前
2026.9.17
数据结构·算法·leetcode
木井巳1 小时前
【记忆化搜索】不同路径
java·算法·leetcode·深度优先·剪枝·推荐算法
boxiansheng1631 小时前
408计算机网络
网络·计算机网络·智能路由器
闲云野鹤在人间2 小时前
docker 入门 | 第5章 网络详解
网络·docker·架构·华为云·php
orange....2 小时前
VMware 双网卡配置:宿主机切换网络时 Ubuntu 固定 IP 访问方案
网络·ubuntu·网络安全
yi碗汤园3 小时前
MQTT消息队列遥测传输协议
网络·网络协议·unity
吴声子夜歌4 小时前
Shell编程实例——高级脚本编程(一)
linux·运维·网络·shell
All for pursuit.4 小时前
【链表-9】146.LRU缓存
数据结构·c++·算法·leetcode
圣保罗的大教堂4 小时前
leetcode 1477. 找两个和为目标值且不重叠的子数组 中等
leetcode
深圳光特通信豆子4 小时前
1×9光模块DDM数字诊断监控技术手册
网络·物联网