LeetCode刷题第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]
提示:
每个链表中的节点数在范围 [1, 100] 内
0 <= Node.val <= 9
题目数据保证列表表示的数字不含前导零
二、解题思路
1、问题理解
我们需要实现一个算法,将两个 逆序存储 的链表相加。每个链表的节点表示一个数字,每个节点存储一位数字,并且从低位到高位排列。例如,链表 l1 表示数字 342,那么 l1 的结构就是:
3 -> 4 -> 2
另一个链表 l2 表示数字 465,它的结构是:
5 -> 6 -> 4
将这两个链表相加后,结果应该是 807,即:
7 -> 0 -> 8
2、解题思路
我们可以通过模拟手动加法来解决这个问题,就像我们手动加法时从个位开始计算,逐位向前加,并考虑进位。
初始化状态:
我们首先定义一个变量 carry 来保存进位的值,初始为 0。这个变量会在每次加法后更新。
创建一个虚拟的头节点 dummy 来简化代码,避免处理边界条件,比如第一个节点的插入。用一个指针 current 来指向当前结果链表的尾部。
循环加法:
只要有一个链表没有遍历完,或者进位不为 0,我们就继续循环:
取出当前两个链表节点的值,如果该链表已经遍历完则默认为 0。
计算当前位的和:
sum = l1_val + l2_val + carry。
计算进位:
carry = sum / 10(即如果 sum 大于等于 10,我们就会有进位)。
当前位的值是
sum % 10(即去掉进位后的结果)。
构建结果链表:
在每次循环中,将当前位的结果 sum % 10 插入到新的链表中。
同时移动链表指针 l1 和 l2,以便继续处理下一个数字。
返回结果:
最后,返回结果链表的头节点(dummy.next)。
三、代码实现及注释
1、源码实现
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
// 创建一个虚拟的头节点,简化链表操作,避免头节点为空的特殊情况
ListNode* head = nullptr;
ListNode* current = nullptr;
// 进位值,初始为0
int carry = 0;
// 只要l1、l2或者进位carry不为0,继续计算
while (l1 != nullptr || l2 != nullptr || carry != 0) {
// 初始化当前位的和为进位值
int sum = carry;
// 如果l1存在,则加上l1的当前位
if (l1 != nullptr) {
sum += l1->val; // l1当前位值加到sum
l1 = l1->next; // l1指针向后移动
}
// 如果l2存在,则加上l2的当前位
if (l2 != nullptr) {
sum += l2->val; // l2当前位值加到sum
l2 = l2->next; // l2指针向后移动
}
// 计算新的进位值
carry = sum / 10; // 如果sum大于等于10,carry将为1,否则为0
// 当前位的值是sum % 10
int digit = sum % 10; // 取当前位的值,可能是0~9
// 创建新节点,存储当前位的值
ListNode* newNode = new ListNode(digit);
// 如果当前链表为空,head指向新节点
if (current == nullptr) {
head = newNode; // 头节点指向当前新节点
current = newNode; // current也指向当前新节点
} else {
// 否则,将新节点链接到链表尾部
current->next = newNode;
current = current->next; // 移动current到新节点
}
}
// 返回结果链表的头节点
return head;
}
};
2、代码解释
ListNode* head = nullptr; ListNode* current = nullptr;
head 用来指向结果链表的头节点。我们将通过 current 指针构建结果链表,并且 current 最终指向链表的尾部。
int carry = 0;
carry 存储每次加法后的进位。初始化为 0。
while (l1 != nullptr || l2 != nullptr || carry != 0)
这个 while 循环保证了每一轮都检查是否需要继续执行:
l1 和 l2 可能已经遍历完,但是进位值可能仍然存在。
只要 l1 或 l2 尚未遍历完,或者进位值 carry 不为 0,就继续循环。
int sum = carry;
每次循环时,sum 会先被赋值为进位值 carry,然后再根据 l1 和 l2 的值更新。
if (l1 != nullptr) { sum += l1->val; l1 = l1->next; }
如果 l1 当前节点存在,我们就把它的值加到 sum 中,然后移动 l1 指针。
if (l2 != nullptr) { sum += l2->val; l2 = l2->next; }
同理,对于 l2,如果当前节点存在,也要加到 sum 中,并移动 l2 指针。
carry = sum / 10;
计算进位,如果 sum 大于或等于 10,carry 就为 1,否则为 0。
int digit = sum % 10;
当前位的数字是 sum % 10,即去掉进位后的个位数字。
ListNode* newNode = new ListNode(digit);
创建一个新的节点,存储当前位的数字。
if (current == nullptr) { head = newNode; current = newNode; }
如果 current 为空,说明这是链表的第一个节点,因此需要将 head 和 current 都指向新节点。
current->next = newNode; current = current->next;
如果已经有节点了,就将新节点添加到链表的末尾,并移动 current 到新节点。
return head;
返回构建好的链表,从 head 开始。
四、执行效果
1、时间和空间复杂度分析
时间复杂度: 每次只遍历 l1 和 l2 中的每个节点一次,所以时间复杂度是 O(max(m, n)),其中 m 和 n 分别是 l1 和 l2 的长度。
空间复杂度: 结果链表的长度最多是 max(m, n) + 1,因此空间复杂度是 O(max(m, n)),主要用于存储结果链表的节点。