LeetCode刷题指南:如何优雅吃掉这道“链表加法”

187 阅读3分钟

一、当链表遇见数学:一道看似简单的面试题

某天面试中,面试官抛来一道题:"给你两个逆序存储数字的链表,如何实现它们的相加?" 我盯着题目陷入沉思——这不就是小时候列竖式计算加法的翻版吗?只不过现在数字被拆解成了链表节点。

1.1 问题重述

我们有两个非空链表,分别表示两个非负整数:

  • 每个节点存储一位数字
  • 数字以逆序方式存储
  • 返回一个新链表表示两数之和

例如:

输入:243 (表示342)  
     564 (表示465)
输出:708 (表示807)

二、算法核心:模拟竖式加法

2.1 人类计算 vs 计算机计算

当我们人类计算342+465时,会这样列竖式:

  3 4 2
+ 4 6 5
-------
  8 0 7

而链表已经帮我们做好了"逆序"这个步骤,这正是算法的巧妙之处。

2.2 代码逐行解析

var addTwoNumbers = function(l1, l2) {
    let dum = new ListNode(); // 哑节点作为结果链表的起点
    let current = dum;       // 当前节点指针
    let carry = 0;           // 进位标志
    
    // 只要任一链表未遍历完或仍有进位,就继续计算
    while(l1 || l2 || carry) {
        let sum = carry;     // 当前位总和初始化为进位值
        
        // 处理链表1的当前位
        if(l1) {
            sum += l1.val;
            l1 = l1.next;
        }
        
        // 处理链表2的当前位
        if(l2) {
            sum += l2.val;
            l2 = l2.next;
        }
        
        carry = Math.floor(sum / 10);          // 计算新的进位
        current.next = new ListNode(sum % 10); // 创建结果节点
        current = current.next;                // 移动指针
    }
    
    return dum.next; // 返回哑节点的下一个节点
};

2.3 关键变量说明

变量名作用类比竖式计算
dum结果链表的哨兵节点预留的答案行
current当前计算位置的指针计算中的竖式行
carry记录进位值写在横线上方的小数字

三、算法图解:一步步拆解执行过程

让我们用示例1来演示算法流程:

image.png

四、边界情况与特殊处理

4.1 链表长度不等的情况

输入:1 → 2 → 3 → 4
      5 → 6

算法会自动处理为:

1 → 2 → 3 → 4
5 → 6 → 0 → 0

4.2 最高位进位的情况

输入:9 → 9
      1

处理过程:

  1. 9+1=10 → 写0进1
  2. 9+0+1=10 → 写0进1
  3. 处理进位1 → 新建节点1
结果:0 → 0 → 1

五、常见误区与优化技巧

5.1 易错点

  1. 忘记处理最后进位

    // 错误写法:缺少carry判断
    while(l1 || l2) { ... }
    
  2. 节点连接顺序错误

    // 错误写法:先移动指针再创建节点
    current = current.next;
    current.next = new ListNode(...);
    

5.2 优化方向

  1. 内存优化:可以复用较长的链表节点
  2. 并行计算:对于超长链表可考虑分块并行处理
  3. 尾递归优化:函数式写法可能更简洁

六、终极挑战:你能解决这些问题吗?

  1. 如果数字是正序存储的(如3→4→2表示342),如何修改算法?
  2. 如何实现三个链表的数字相加?
  3. 如果要支持小数运算,算法该如何调整?

"算法之美,在于将复杂问题拆解为简单步骤的智慧。" —— 匿名程序员