LeetCode 2. 两数相加:用链表模拟「竖式加法」的完整思路

11 阅读4分钟

一、题目回顾

给你两个 非空链表 l1l2,它们表示两个非负整数。

数字的存储方式是:

  • 逆序存储
  • 每个节点存一位数字

要求:
返回一个新的链表,表示这两个数相加的结果,同样是逆序存储。

示例:

l1: 2 -> 4 -> 3
l2: 5 -> 6 -> 4

表示的数字:
342 + 465 = 807

返回:
7 -> 0 -> 8

二、这道题本质在考什么?

表面看是链表,其实核心是:

如何用链表,模拟人类的“竖式加法”

拆开来看,只做三件事:

  1. 对应位相加
  2. 处理进位
  3. 结果一位一位接到新链表上

关键难点不在算法,而在于:

  • 链表怎么一边遍历,一边构建新链表
  • 进位如何在不同长度的链表中正确传递

三、整体解题思路

整体策略可以总结为一句话:

同时遍历两个链表,把每一位的和算出来,进位留到下一位

实现上分为四个阶段:

  1. 同时遍历 l1l2
  2. 处理剩余较长的链表
  3. 最后检查是否还有进位
  4. 返回真正的结果链表

四、代码整体结构

先看完整代码,再逐块拆解:

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;
}

这段代码完整地模拟了「竖式加法的一列」。

逐步理解:

  1. 当前位的和 = 两个节点的值 + 上一位进位
  2. total / 10 是新的进位
  3. total % 10 是当前位的结果
  4. 把结果节点接到结果链表末尾
  5. 两个指针同时向后移动

这一段处理的是:

两个链表都还有数字的情况


七、为什么要分开处理“剩余链表”?

链表长度可能不同,例如:

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))

    • 结果链表占用的空间

十、总结

这道题的关键不在链表本身,而在于:

把人类的“按位加法”过程,精确地翻译成代码

几个非常值得反复体会的点:

  1. 用虚拟头节点简化链表构建
  2. 把进位当成一个“跨轮次状态”
  3. 不同长度链表要分阶段处理
  4. 最后一定要检查是否还有进位

理解透这一题后,你会发现:

  • 很多链表题,本质都是“遍历 + 状态传递”
  • 思路清楚了,代码自然就顺了