【LeetCode Hot100 刷题日记(28/100)】2.两数相加 —— 链表、数学、模拟与进位处理🔄

97 阅读6分钟

📌 题目链接:2. 两数相加 - 力扣(LeetCode)

🔍 难度:中等 | 🏷️ 标签:链表、数学、模拟

⏱️ 目标时间复杂度:O(max(m,n))

💾 空间复杂度:O(1)(不计返回链表空间)


✅ 本期我们深入剖析 LeetCode 热题 100 中的第 28 题2.两数相加。这道题看似简单,实则考察了对链表结构的熟练掌握、数学进位逻辑的精准模拟,以及边界情况的严谨处理能力。是面试中常考的经典链表题之一!🔥


🎯 题目分析

给你两个 非空链表,表示两个非负整数,数字按 逆序 存储在链表中(即个位在前,高位在后)。例如:

  • l1 = [2,4,3] 表示数字 342
  • l2 = [5,6,4] 表示数字 465

要求你将这两个数相加,并以相同形式返回一个新链表。

📌 关键点

  • 数字是逆序存储 → 可直接从头开始逐位相加
  • 每个节点只能存一位数字(0~9)
  • 可能存在进位 → 需要维护 carry
  • 两个链表长度可能不同 → 短链表后续视为 0
  • 最后一位若仍有进位,需额外添加节点

🎯 输出示例
[7,0,8] 对应 807,因为 342 + 465 = 807


🔍 核心算法及代码讲解

🧮 核心思想:模拟竖式加法

这是典型的「模拟」类题目,核心在于模仿我们小学学过的 竖式加法过程

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

但注意:由于链表是逆序存储,所以我们可以直接从左到右逐位相加!

✅ 关键步骤分解:

步骤描述
1️⃣初始化 headtail 指针用于构建结果链表
2️⃣使用 carry 记录进位(初始为 0)
3️⃣同时遍历 l1l2,直到两者都为空
4️⃣当前位和 = n1 + n2 + carry
5️⃣新节点值 = (sum % 10),进位 = sum / 10
6️⃣构建新节点并连接到结果链表尾部
7️⃣若最后还有进位,追加一个值为 carry 的节点

💡 进位机制详解:

int sum = n1 + n2 + carry;
int digit = sum % 10;        // 当前位数字
int new_carry = sum / 10;    // 新的进位(0 或 1)

⚠️ 注意:由于每位最多是 9,两个数加起来最大是 9+9+1=19,所以进位最多为 1,不会超过 1。


🧩 解题思路(分步拆解)

  1. 初始化变量

    • head: 结果链表头指针(首次创建时赋值)
    • tail: 当前尾节点(用于不断追加新节点)
    • carry: 进位值,初始为 0
  2. 循环遍历两个链表

    • 使用 while (l1 || l2) 确保即使其中一个链表结束,仍继续处理另一个
    • 安全取值:n1 = l1 ? l1->val : 0,避免空指针访问
  3. 计算当前位和

    • sum = n1 + n2 + carry
    • digit = sum % 10
    • carry = sum / 10
  4. 构造新节点

    • 如果 head 为空 → 创建第一个节点
    • 否则 → tail->next = new ListNode(digit) 并更新 tail
  5. 移动指针

    • l1 不为空,则 l1 = l1->next
    • l2 不为空,则 l2 = l2->next
  6. 处理最终进位

    • carry > 0,说明最高位有进位(如 999 + 1 = 1000),需追加一个节点
  7. 返回结果

    • 返回 head 即可

📊 算法分析

项目分析
✅ 时间复杂度O(max(m,n)) 其中 m、n 是两个链表的长度 我们需要遍历最长的那个链表一次
✅ 空间复杂度O(max(m,n)) 结果链表的空间与输入长度有关 ⚠️ 但通常说空间复杂度为 O(1),因为不计入返回值空间(面试中常见说法)
✅ 是否原地操作❌ 不是原地修改,创建了新的链表
✅ 是否递归❌ 迭代实现,更高效稳定
✅ 面试考点链表操作、边界处理、进位逻辑、空指针安全、构造新链表

💡 面试加分点

  • 能清晰说出“为什么用 while(l1 || l2)”?
  • 能解释“为什么进位最多为 1”?
  • 能写出边界测试用例(如 [9,9,9] + [1])?

💻 代码(保留原始模板,完整注释)

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

// Definition for singly-linked list.
struct ListNode {
    int val;
    ListNode *next;
    ListNode() : val(0), next(nullptr) {}
    ListNode(int x) : val(x), next(nullptr) {}
    ListNode(int x, ListNode *next) : val(x), next(next) {}
};

class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        // 初始化结果链表的头和尾指针
        ListNode *head = nullptr, *tail = nullptr;
        
        // 进位变量,初始为 0
        int carry = 0;
        
        // 循环条件:只要 l1 或 l2 还有节点,就继续处理
        while (l1 || l2) {
            // 安全获取当前位的值(如果链表已结束,值为 0)
            int n1 = l1 ? l1->val : 0;
            int n2 = l2 ? l2->val : 0;
            
            // 计算当前位总和(包括上一位的进位)
            int sum = n1 + n2 + carry;
            
            // 当前位数字(模 10)
            int digit = sum % 10;
            
            // 更新进位(除以 10)
            carry = sum / 10;
            
            // 构造新节点
            if (!head) {
                // 第一个节点:初始化 head 和 tail
                head = tail = new ListNode(digit);
            } else {
                // 非首个节点:追加到尾部
                tail->next = new ListNode(digit);
                tail = tail->next;
            }
            
            // 移动指针到下一个节点
            if (l1) {
                l1 = l1->next;
            }
            if (l2) {
                l2 = l2->next;
            }
        }
        
        // 处理最后的进位(如 999 + 1 = 1000)
        if (carry > 0) {
            tail->next = new ListNode(carry);
        }
        
        return head;
    }
};

// 测试
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);

    // 示例 1: [2,4,3] + [5,6,4] = [7,0,8]
    ListNode* l1 = new ListNode(2);
    l1->next = new ListNode(4);
    l1->next->next = new ListNode(3);

    ListNode* l2 = new ListNode(5);
    l2->next = new ListNode(6);
    l2->next->next = new ListNode(4);

    Solution sol;
    ListNode* result = sol.addTwoNumbers(l1, l2);

    // 打印结果
    while (result) {
        cout << result->val << " ";
        result = result->next;
    }
    cout << endl;

    return 0;
}

📌 运行输出7 0 8


🧪 测试用例验证

输入输出说明
[2,4,3], [5,6,4][7,0,8]基础案例
[0], [0][0]边界情况
[9,9,9,9,9,9,9], [9,9,9,9][8,9,9,9,0,0,0,1]长链表 + 进位
[1], [9,9][0,0,1]进位传播

✅ 所有测试通过!


🌟 本期完结,下期见!🔥

👉 点赞收藏加关注,新文更新不迷路。关注专栏【算法】LeetCode Hot100刷题日记,持续为你拆解每一道热题的底层逻辑与面试技巧!

💬 欢迎留言交流你的解法或疑问!一起进步,冲向 Offer!💪


📣 下一期预告:LeetCode 热题 100 第29题 —— 19.删除链表的倒数第 N 个结点 🚫

🔹 题目:给定一个链表,删除其倒数第 N 个节点(假设链表至少有 N 个节点)。

🔹 核心思路:使用双指针(快慢指针),先让快指针走 N 步,然后快慢指针同时前进,当快指针到达末尾时,慢指针指向的就是目标节点的前一个节点。

🔹 考点:链表、双指针、边界处理、虚拟头节点技巧。

🔹 难度:中等,是链表操作中的经典题型,常出现在大厂笔试中。

💡 提示:不要用两次遍历!要用一次遍历完成,且注意 N 可能等于链表长度的情况!

📌 记住:当你在刷题时,不要只看答案,要像写这篇文章一样,深入思考每一步背后的原理、优化空间和面试价值。这才是真正提升算法能力的方式!


本篇总结

  • 掌握了链表模拟加法的核心逻辑
  • 理解了进位机制与边界处理
  • 学会了如何安全遍历两个长度不同的链表
  • 代码结构清晰,适合面试复述与手写

📌 推荐练习


🔁 坚持每日一题,算法不再难!
🚀 下一站:删除链表倒数第 N 个节点,我们不见不散!