一、当链表遇见数学:一道看似简单的面试题
某天面试中,面试官抛来一道题:"给你两个逆序存储数字的链表,如何实现它们的相加?" 我盯着题目陷入沉思——这不就是小时候列竖式计算加法的翻版吗?只不过现在数字被拆解成了链表节点。
1.1 问题重述
我们有两个非空链表,分别表示两个非负整数:
- 每个节点存储一位数字
- 数字以逆序方式存储
- 返回一个新链表表示两数之和
例如:
输入:2→4→3 (表示342)
5→6→4 (表示465)
输出:7→0→8 (表示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来演示算法流程:
四、边界情况与特殊处理
4.1 链表长度不等的情况
输入:1 → 2 → 3 → 4
5 → 6
算法会自动处理为:
1 → 2 → 3 → 4
5 → 6 → 0 → 0
4.2 最高位进位的情况
输入:9 → 9
1
处理过程:
- 9+1=10 → 写0进1
- 9+0+1=10 → 写0进1
- 处理进位1 → 新建节点1
结果:0 → 0 → 1
五、常见误区与优化技巧
5.1 易错点
-
忘记处理最后进位:
// 错误写法:缺少carry判断 while(l1 || l2) { ... } -
节点连接顺序错误:
// 错误写法:先移动指针再创建节点 current = current.next; current.next = new ListNode(...);
5.2 优化方向
- 内存优化:可以复用较长的链表节点
- 并行计算:对于超长链表可考虑分块并行处理
- 尾递归优化:函数式写法可能更简洁
六、终极挑战:你能解决这些问题吗?
- 如果数字是正序存储的(如
3→4→2表示342),如何修改算法? - 如何实现三个链表的数字相加?
- 如果要支持小数运算,算法该如何调整?
"算法之美,在于将复杂问题拆解为简单步骤的智慧。" —— 匿名程序员