0002.两数相加
难度:🟡 中等
标签:链表
、数学
、递归
链接:2. 两数相加
题目描述
给你两个 非空 的链表,表示两个非负整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例 1:
输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807.
示例 2:
输入:l1 = [0], l2 = [0]
输出:[0]
示例 3:
输入:l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9]
输出:[8,9,9,9,0,0,0,1]
解题思路
核心思想
本题的核心是模拟我们小学时学过的竖式加法。由于链表的数字是逆序存储的,链表头节点对应的是数字的个位,这恰好与我们做加法时从最低位(个位)开始计算的顺序一致。我们需要从两个链表的头部开始,逐位相加,并处理好每一位的进位。
思路选择
迭代法 + 虚拟头节点(Dummy Head) 是解决此问题的标准最优思路。
虚拟头节点:创建一个临时的
dummy
节点作为新链表的伪头节点。这样做的好处是,我们可以用一个统一的逻辑来处理所有节点的插入,而无需为新链表的第一个节点编写特殊的处理代码。迭代:使用一个
while
循环,只要两个链表中还有一个有节点,或者最后还有一个进位,就持续进行加法操作。
关键步骤
初始化:创建一个虚拟头节点
dummy = new ListNode(0)
,一个current
指针指向它,以及一个进位变量carry = 0
。循环计算:当
l1
或l2
不为空,或者carry
不为 0 时,持续循环。 a. 取值:获取l1
和l2
当前节点的值,如果某个链表已经遍历完,则其值视为0
。 b. 求和:计算当前位的和sum = val1 + val2 + carry
。 c. 创建新节点:用sum % 10
的值创建一个新节点,并将其链接到current.next
。 d. 更新进位:更新carry = Math.floor(sum / 10)
。 e. 移动指针:将current
指针和l1
、l2
(如果非空)的指针都向后移动一位。返回结果:循环结束后,
dummy.next
指向的就是结果链表的真正头节点。
代码实现
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} l1
* @param {ListNode} l2
* @return {ListNode}
*/
var addTwoNumbers = function(l1, l2) {
const dummyHead = new ListNode(0);
let current = dummyHead;
let carry = 0;
while (l1 !== null || l2 !== null || carry !== 0) {
const val1 = l1 ? l1.val : 0;
const val2 = l2 ? l2.val : 0;
const sum = val1 + val2 + carry;
carry = Math.floor(sum / 10);
current.next = new ListNode(sum % 10);
current = current.next;
if (l1) l1 = l1.next;
if (l2) l2 = l2.next;
}
return dummyHead.next;
};
复杂度分析
时间复杂度:O(max(m, n)) 其中 m 和 n 分别是两个链表的长度。我们需要遍历完两个链表中较长的那一个。
空间复杂度:O(max(m, n)) 结果链表的长度最多为
max(m, n) + 1
,因此空间消耗与最长链表的长度成正比。
相关题目
总结
本题是链表操作的经典入门题。它考察了对链表的基本遍历、节点创建以及处理进位等细节的能力。虚拟头节点(Dummy Head)的技巧在本题中尤为实用,大大简化了代码逻辑。