Bootstrap

LeetCode刷题第2题【两数相加】---解题思路及源码注释

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)),主要用于存储结果链表的节点。

;