【力扣100题】14.两数相加

1. 题目描述

给你两个非空链表,表示两个非负整数。每位数字都是按照逆序存储的,并且每个节点只能存储一位数字。

请你将两个数相加,并以相同形式返回一个表示和的链表。

你可以假设除了数字 0 之外,这两个数都不会以 0 开头。

示例

复制代码
输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807

输入:l1 = [0], l2 = [0]
输出:[0]

输入:l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9]
输出:[8,9,9,9,0,0,0,1]

2. 核心思想

关键思想:链表模拟竖式加法

两个数字按逆序存储,从低位到高位逐位相加,满十进一

复制代码
    3 → 4 → 2    (数字 342)
  + 4 → 6 → 5    (数字 465)
  ────────────
    7 → 0 → 8    (数字 807)

核心要点:

  • 逐节点遍历:l1.val + l2.val + carry
  • 进位 carry = sum / 10
  • 当前位 sum % 10
  • 最后还要检查进位(如 999 + 1 = 1000)

3. 多种方法解决

方法一:虚拟头节点(推荐)✅

cpp 复制代码
/**
 * 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* dummy = new ListNode(0);  // 虚拟头节点
        ListNode* head = dummy;              // 移动指针
        int carry = 0;                       // 进位

        while (l1 || l2) {
            int x = l1 ? l1->val : 0;
            int y = l2 ? l2->val : 0;
            int sum = x + y + carry;

            carry = sum / 10;                     // 更新进位
            head->next = new ListNode(sum % 10);  // 创建新节点
            head = head->next;                    // 移动指针

            if (l1) l1 = l1->next;
            if (l2) l2 = l2->next;
        }

        // 处理最后的进位(如 999 + 1 = 1000)
        if (carry) {
            head->next = new ListNode(carry);
        }

        return dummy->next;  // 跳过虚拟头节点
    }
};

复杂度: 时间 O(max(m,n)),空间 O(max(m,n))


方法二:递归写法

cpp 复制代码
class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        return addTwoNumbersHelper(l1, l2, 0);
    }

private:
    ListNode* addTwoNumbersHelper(ListNode* l1, ListNode* l2, int carry) {
        // 递归终止条件:两个链表都走完且没有进位
        if (!l1 && !l2 && carry == 0) {
            return nullptr;
        }

        int x = l1 ? l1->val : 0;
        int y = l2 ? l2->val : 0;
        int sum = x + y + carry;

        ListNode* node = new ListNode(sum % 10);
        node->next = addTwoNumbersHelper(
            l1 ? l1->next : nullptr,
            l2 ? l2->next : nullptr,
            sum / 10
        );

        return node;
    }
};

复杂度: 时间 O(max(m,n)),空间 O(max(m,n))(递归栈)


方法三:先反转链表再做(不推荐)

  1. 先把两个链表反转
  2. 按正常顺序做加法
  3. 再把结果反转
cpp 复制代码
// 步骤1:反转链表
ListNode* reverseList(ListNode* head) {
    ListNode* prev = nullptr;
    ListNode* curr = head;
    while (curr) {
        ListNode* next = curr->next;
        curr->next = prev;
        prev = curr;
        curr = next;
    }
    return prev;
}

// 步骤2:普通加法
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
    l1 = reverseList(l1);
    l2 = reverseList(l2);
    // ... 普通加法逻辑 ...
    return reverseList(result);
}

复杂度: 时间 O(m+n),空间 O(1),但代码复杂,不推荐 ❌


4. 图解过程

示例:l1 = [2,4,3], l2 = [5,6,4]

复制代码
初始状态:
dummy → nullptr
carry = 0

┌─────────────────────────────────────────┐
│  第1位(个位):2 + 5 + 0 = 7            │
│                                         │
│  l1: 2 → 4 → 3                         │
│  l2: 5 → 6 → 4                         │
│  sum = 7, carry = 0                     │
│                                         │
│  dummy → [7]                            │
└─────────────────────────────────────────┘

┌─────────────────────────────────────────┐
│  第2位(十位):4 + 6 + 0 = 10           │
│                                         │
│  sum = 10, carry = 1                    │
│  当前位:0                               │
│                                         │
│  dummy → [7] → [0]                     │
└─────────────────────────────────────────┘

┌─────────────────────────────────────────┐
│  第3位(百位):3 + 4 + 1 = 8            │
│                                         │
│  sum = 8, carry = 0                    │
│                                         │
│  dummy → [7] → [0] → [8]               │
└─────────────────────────────────────────┘

遍历结束,carry = 0,无需额外节点

结果:7 → 0 → 8  ✓

边界情况:l1 = [9,9,9], l2 = [1]

复制代码
第1位:9 + 1 + 0 = 10 → 0, carry=1
第2位:9 + 0 + 1 = 10 → 0, carry=1
第3位:9 + 0 + 1 = 10 → 0, carry=1
遍历完:l1 结束,l2 结束,但 carry=1
额外节点:1

结果:0 → 0 → 0 → 1  ✓  (1000)

5. 方法优缺点比较

方法 时间 空间 优点 缺点
虚拟头节点 O(max(m,n)) O(max(m,n)) ✅ 代码清晰,逻辑直接 需要 new 节点
递归 O(max(m,n)) O(max(m,n)) ✅ 代码简洁 ❌ 递归栈占用,可能栈溢出
先反转链表 O(m+n) O(1) ✅ 空间最优 ❌ 代码复杂,多遍历2次,不推荐

推荐方法

方法一(虚拟头节点) 是面试和竞赛中最常用的写法,理由:

  1. 逻辑直观,不易出错
  2. 无递归栈溢出风险
  3. 代码可读性高

6. 完整测试代码

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

struct ListNode {
    int val;
    ListNode *next;
    ListNode(int x) : val(x), next(nullptr) {}
};

class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        ListNode* dummy = new ListNode(0);
        ListNode* head = dummy;
        int carry = 0;

        while (l1 || l2) {
            int x = l1 ? l1->val : 0;
            int y = l2 ? l2->val : 0;
            int sum = x + y + carry;

            carry = sum / 10;
            head->next = new ListNode(sum % 10);
            head = head->next;

            if (l1) l1 = l1->next;
            if (l2) l2 = l2->next;
        }

        if (carry) {
            head->next = new ListNode(carry);
        }

        return dummy->next;
    }
};

// 辅助函数:链表转向量
vector<int> toVector(ListNode* head) {
    vector<int> result;
    while (head) {
        result.push_back(head->val);
        head = head->next;
    }
    return result;
}

// 辅助函数:向量转链表
ListNode* toList(vector<int> vals) {
    ListNode* dummy = new ListNode(0);
    ListNode* head = dummy;
    for (int v : vals) {
        head->next = new ListNode(v);
        head = head->next;
    }
    return dummy->next;
}

int main() {
    Solution sol;

    // 测试1:342 + 465 = 807
    ListNode* l1 = toList({2, 4, 3});
    ListNode* l2 = toList({5, 6, 4});
    vector<int> r1 = toVector(sol.addTwoNumbers(l1, l2));
    cout << "342 + 465 = ";
    for (int v : r1) cout << v;
    cout << " (expected: 708)" << endl;

    // 测试2:0 + 0 = 0
    vector<int> r2 = toVector(sol.addTwoNumbers(toList({0}), toList({0})));
    cout << "0 + 0 = " << r2[0] << " (expected: 0)" << endl;

    // 测试3:9999999 + 9999 = 10009998
    ListNode* l5 = toList({9,9,9,9,9,9,9});
    ListNode* l6 = toList({9,9,9,9});
    vector<int> r3 = toVector(sol.addTwoNumbers(l5, l6));
    cout << "9999999 + 9999 = ";
    for (int v : r3) cout << v;
    cout << " (expected: 10009998)" << endl;

    return 0;
}

输出:

复制代码
342 + 465 = 708 (expected: 708)
0 + 0 = 0 (expected: 0)
9999999 + 9999 = 10009998 (expected: 10009998)

8. 总结

要点 说明
核心思想 链表模拟竖式加法
虚拟头节点 简化头节点处理
进位处理 遍历结束后还要检查 carry
边界条件 链表长度不同、空链表
推荐写法 虚拟头节点(方法一)
相关推荐
AlanW2 小时前
# Vcpkg使用总结2
c++
我不是小upper2 小时前
相关≠因果!机器学习中皮尔逊相关检验的完整流程
人工智能·算法·机器学习
float_com2 小时前
LeetCode80. 删除有序数组中的重复项 II
leetcode
pwn蒸鱼2 小时前
leetcode:21. 合并两个有序链表
算法·leetcode·链表
洛水水2 小时前
【力扣100题】15.删除链表的倒数第 N 个结点
算法·leetcode·链表
LTphy2 小时前
深度优先搜索的三种模板
算法·深度优先·图论
paeamecium2 小时前
【PAT甲级真题】- Insert or Merge (25)
数据结构·c++·算法·排序算法·pat考试·pat
不爱吃炸鸡柳2 小时前
6道经典算法题详解:从排序到链表,覆盖面试高频考点
算法·链表·面试
wfbcg2 小时前
每日算法练习:LeetCode 3. 无重复字符的最长子串 ✅
算法·leetcode·职场和发展