一、题目完整详情
1. 原题描述
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
2. 核心题意拆解(必看)
-
存储规则 :链表逆序存储数字,即链表第一个节点是数字的个位 ,第二个节点是十位 ,依次往后。例如:数字342 → 链表
2→4→3 -
节点规则:单个节点仅存储 0-9 的一位数字,无多位数
-
输入规则:两个链表均非空,数字无前置零(仅数字0本身允许单个0节点)
-
输出规则 :相加结果同样以逆序链表返回
3. 经典示例深度解析
-
示例1:输入 l1=[2,4,3]、l2=[5,6,4],对应数字 342+465=807,输出逆序链表 [7,0,8]
-
示例2:输入 l1=[0]、l2=[0],对应数字 0+0=0,输出 [0]
-
示例3:输入 l1=[9,9,9,9,9,9,9]、l2=[9,9,9,9],对应数字 9999999+9999=10009998,输出逆序链表 [8,9,9,9,0,0,0,1]
4. 题目难点
-
链表长度不统一,需要兼容长短链表相加
-
加法进位处理,尤其是最后一位产生进位的边界情况
-
链表无法直接随机访问,只能从头遍历
-
大数场景下,直接转换数字会溢出失效
二、零基础必备前置知识
本节包含本题用到的所有基础知识点,从未接触过编程、算法、链表的,看完即可完全掌握解题基础。
1. 单链表数据结构(本题核心)
1.1 什么是单链表?
单链表是一种线性链式存储的数据结构,由多个「节点」串联组成,类比现实中的糖葫芦:每一颗山楂是节点,串联的绳子是指针。
与数组不同,链表的内存地址不连续,只能通过指针依次访问,不能直接跳转到指定位置。
1.2 链表节点的组成
本题的标准节点固定包含两个核心属性:
-
数据域 val:存储当前节点的数值,本题固定为 0-9 的整数
-
指针域 next :存储下一个节点的地址,用于串联链表
链表末尾节点的 next 指向 空值(Python:None / C++:nullptr),代表链表结束。
1.3 链表核心特性
-
必须从头节点开始遍历,无法直接访问中间、末尾节点
-
支持动态新增节点,长度不固定
-
逆序存储适配加法:个位在前,刚好对应数学加法从低位开始计算的规则
2. 十进制加法进位规则(算法核心逻辑)
本题算法完全模拟小学竖式加法,所有计算逻辑基于以下规则:
-
加法顺序:从**最低位(个位)**向高位依次相加
-
计算公式:当前位总和 = 第一个数当前位 + 第二个数当前位 + 上一位的进位值
-
进位规则:总和 ≥10 则产生进位,进位值固定为1;总和<10 进位值为0
-
当前位结果:总和对10取余(保留个位数字)
-
收尾规则:所有位数相加完成后,若仍有进位,需要单独新增一位存储进位
示例:9+9=18 → 当前位存8,进位1;最后剩余进位1,需新增节点存储1
3. 代码基础运算知识
-
取余运算 % :
a % 10,获取数字的个位(例:18%10=8、7%10=7) -
整除运算 // / :Python用
//、C++用/,获取数字的十位及以上(例:18//10=1、7//10=0) -
空值判断:链表节点遍历结束后为空,空节点数值统一取0参与计算
4. 算法常用技巧:哑节点(哨兵节点)
4.1 定义
哑节点是一个无实际业务意义的虚拟头节点,数值固定为0,不参与计算,仅用于拼接结果链表。
4.2 作用(解题关键)
如果不使用哑节点,需要单独判断结果链表的第一个节点,代码冗余、逻辑复杂;使用哑节点后,所有节点统一拼接在哑节点后方,无需处理头节点特殊情况,代码逻辑统一、简洁。
4.3 使用方式
最终结果为:哑节点的下一个节点(dummy.next)
5. 链表遍历规则
-
定义指针变量,初始指向链表头节点
-
每完成一次当前节点计算,指针向后移动:指针 = 指针.next
-
循环终止条件:指针为空,代表链表遍历完成
三、解题思路(由易到难:暴力法→最优优化法)
解法一:暴力转换数字法(入门简单,适合理解)
1. 核心思路
跳过链表逐位计算,直接将两个逆序链表转换为完整整数 ,整数相加后,再将结果整数逆序转换为链表。逻辑直观,完全贴合题目字面意思。
2. 执行步骤
-
遍历第一个链表,逆序拼接为完整整数 num1
-
遍历第二个链表,逆序拼接为完整整数 num2
-
计算总和 sum_num = num1 + num2
-
将 sum_num 逐位拆分,逆序构建结果链表
3. 优缺点分析
-
优点:逻辑极简,零基础极易理解,代码可读性高
-
缺点:存在大数溢出问题,题目支持链表长度最大100位,远超普通整数、长整数存储范围,无法通过超长用例,仅适合入门学习
解法二:逐位模拟进位法(最优解法、面试标准、AC满分)
1. 核心思路
完全模拟小学竖式加法,不转换完整数字,直接遍历两个链表,逐位相加、更新进位、构建结果链表,规避大数溢出问题,是本题唯一通用解法。
2. 执行步骤
-
初始化哑节点、遍历指针、进位变量(初始进位为0)
-
循环遍历:只要任意链表未遍历完 或 存在进位,持续循环
-
取出两个链表当前位数值(空节点补0)
-
计算当前位总和、更新进位、生成当前结果位
-
新建节点拼接至结果链表,移动所有遍历指针
-
循环结束,返回哑节点下的结果链表
3. 优缺点分析
-
优点:无溢出问题,兼容所有边界用例,时间空间最优,可直接提交AC
-
缺点:需要理解进位循环逻辑,比暴力法稍抽象
四、Python 完整代码实现(双解法+逐行注释+全测试用例+自定义输入)
1. 暴力转换数字法(入门版)
python
# 定义链表节点类(题目标准模板,不可修改)
class ListNode:
# 节点构造方法:默认值0,默认无下一个节点
def __init__(self, val=0, next=None):
self.val = val # 节点存储的数值
self.next = next # 指向后继节点的指针
from typing import Optional
class Solution:
# 暴力解法:链表转数字相加
def addTwoNumbers(self, l1: Optional[ListNode], l2: Optional[ListNode]) -> Optional[ListNode]:
# 1. 初始化两个数字的基数和结果值
num1 = 0
num2 = 0
base = 1 # 基数:个位1、十位10、百位100...对应逆序链表
# 2. 遍历l1链表,转换为完整整数
while l1:
num1 += l1.val * base # 当前位数值*基数,累加得到完整数
base *= 10 # 基数升级(个位→十位→百位)
l1 = l1.next # 指针后移
# 重置基数,遍历l2链表
base = 1
while l2:
num2 += l2.val * base
base *= 10
l2 = l2.next
# 3. 两数直接相加
sum_num = num1 + num2
# 4. 将总和逆序转为链表
# 处理特殊情况:和为0
if sum_num == 0:
return ListNode(0)
# 创建哑节点用于拼接结果
dummy = ListNode(0)
cur = dummy
# 逐位拆分总和,构建逆序链表
while sum_num > 0:
cur.next = ListNode(sum_num % 10) # 取个位作为当前节点值
sum_num = sum_num // 10 # 去掉个位,剩余高位继续拆分
cur = cur.next # 指针后移
# 返回结果链表头节点
return dummy.next
# ====================== 工具函数:测试专用 ======================
def create_linked_list(nums):
"""根据数字列表快速创建链表"""
dummy = ListNode(0)
cur = dummy
for num in nums:
cur.next = ListNode(num)
cur = cur.next
return dummy.next
def print_linked_list(head):
"""格式化打印链表,输出样式:7→0→8"""
res = []
cur = head
while cur:
res.append(str(cur.val))
cur = cur.next
print("→".join(res))
def custom_input():
"""自定义用户输入,手动输入两个链表"""
print("\n===== 自定义输入测试 =====")
print("提示:输入逆序数字,例如342输入 2 4 3,空格分隔,回车结束")
nums1 = list(map(int, input("请输入第一个链表数字:").split()))
nums2 = list(map(int, input("请输入第二个链表数字:").split()))
return create_linked_list(nums1), create_linked_list(nums2)
# ====================== 主函数:全用例测试 ======================
if __name__ == "__main__":
sol = Solution()
# 测试用例1:官方示例1
print("【测试用例1 官方示例】")
l1 = create_linked_list([2,4,3])
l2 = create_linked_list([5,6,4])
print("输入链表1:", end="")
print_linked_list(l1)
print("输入链表2:", end="")
print_linked_list(l2)
print("运算结果:", end="")
print_linked_list(sol.addTwoNumbers(l1, l2))
# 测试用例2:官方示例2
print("\n【测试用例2 官方示例】")
l1 = create_linked_list([0])
l2 = create_linked_list([0])
print("输入链表1:", end="")
print_linked_list(l1)
print("输入链表2:", end="")
print_linked_list(l2)
print("运算结果:", end="")
print_linked_list(sol.addTwoNumbers(l1, l2))
# 测试用例3:官方示例3
print("\n【测试用例3 官方示例】")
l1 = create_linked_list([9,9,9,9,9,9,9])
l2 = create_linked_list([9,9,9,9])
print("输入链表1:", end="")
print_linked_list(l1)
print("输入链表2:", end="")
print_linked_list(l2)
print("运算结果:", end="")
print_linked_list(sol.addTwoNumbers(l1, l2))
# 自定义输入测试
l1_custom, l2_custom = custom_input()
print("自定义运算结果:", end="")
print_linked_list(sol.addTwoNumbers(l1_custom, l2_custom))
2. 最优模拟进位法(满分AC版,逐行超详细注释)
python
# 定义链表节点类(LeetCode官方标准模板)
class ListNode:
# 构造函数:初始化节点值和后继节点
def __init__(self, val=0, next=None):
self.val = val # 节点存储的单个数字
self.next = next # 指向后续节点的指针
# 导入类型注解,适配LeetCode编译规范
from typing import Optional
class Solution:
def addTwoNumbers(self, l1: Optional[ListNode], l2: Optional[ListNode]) -> Optional[ListNode]:
"""
最优解法:逐位模拟竖式加法
:param l1: 第一个逆序数字链表
:param l2: 第二个逆序数字链表
:return: 相加结果的逆序链表
"""
# 1. 初始化哑节点:虚拟头节点,统一拼接逻辑,无需处理首节点特殊情况
dummy_node = ListNode(0)
# 2. 遍历指针:专门用于构建、移动结果链表
current = dummy_node
# 3. 进位变量:存储每一位相加的进位,初始无进位为0
carry = 0
# 4. 核心循环:满足任意条件则继续遍历
# 条件1:l1未遍历完 条件2:l2未遍历完 条件3:存在剩余进位
while l1 is not None or l2 is not None or carry != 0:
# 空节点补0:如果当前链表遍历完毕,当前位数值取0
val1 = l1.val if l1 else 0
val2 = l2.val if l2 else 0
# 5. 计算当前位总和:两个当前位数值 + 上一轮进位
total = val1 + val2 + carry
# 6. 更新进位:总和十位为新进位(0或1)
carry = total // 10
# 7. 计算当前位最终结果:总和个位为当前节点值
cur_val = total % 10
# 8. 新建结果节点,拼接至结果链表尾部
current.next = ListNode(cur_val)
# 9. 结果指针后移,等待拼接下一个节点
current = current.next
# 10. 原链表指针后移,遍历下一位,空则保持空
if l1:
l1 = l1.next
if l2:
l2 = l2.next
# 11. 返回哑节点的下一个节点(结果链表真正的头节点)
return dummy_node.next
# ====================== 通用工具函数 ======================
def create_linked_list(nums):
"""数字列表转逆序链表"""
dummy = ListNode(0)
cur = dummy
for num in nums:
cur.next = ListNode(num)
cur = cur.next
return dummy.next
def print_linked_list(head):
"""格式化打印链表"""
res = []
cur = head
while cur:
res.append(str(cur.val))
cur = cur.next
print("→".join(res))
def custom_input():
"""自定义用户手动输入"""
print("\n===== 自定义输入测试 =====")
nums1 = list(map(int, input("请输入第一个链表逆序数字:").split()))
nums2 = list(map(int, input("请输入第二个链表逆序数字:").split()))
return create_linked_list(nums1), create_linked_list(nums2)
# ====================== 主函数:全覆盖测试 ======================
if __name__ == "__main__":
sol = Solution()
# 官方测试用例1
print("【官方测试用例1】")
l1, l2 = create_linked_list([2,4,3]), create_linked_list([5,6,4])
print("输入1:", end=""); print_linked_list(l1)
print("输入2:", end=""); print_linked_list(l2)
print("结果:", end=""); print_linked_list(sol.addTwoNumbers(l1, l2))
# 官方测试用例2
print("\n【官方测试用例2】")
l1, l2 = create_linked_list([0]), create_linked_list([0])
print("输入1:", end=""); print_linked_list(l1)
print("输入2:", end=""); print_linked_list(l2)
print("结果:", end=""); print_linked_list(sol.addTwoNumbers(l1, l2))
# 官方测试用例3
print("\n【官方测试用例3】")
l1, l2 = create_linked_list([9,9,9,9,9,9,9]), create_linked_list([9,9,9,9])
print("输入1:", end=""); print_linked_list(l1)
print("输入2:", end=""); print_linked_list(l2)
print("结果:", end=""); print_linked_list(sol.addTwoNumbers(l1, l2))
# 自定义输入测试
l1_c, l2_c = custom_input()
print("自定义运算结果:", end=""); print_linked_list(sol.addTwoNumbers(l1_c, l2_c))
3. 最优解法详细运行流程(以示例1为例)
测试用例:l1=[2,4,3]、l2=[5,6,4],目标:342+465=807,输出[7,0,8]
-
初始化:dummy=0,current=dummy,carry=0
-
第一轮循环(个位相加):val1=2、val2=5,total=7,carry=0,当前值7,结果链表:0→7,指针后移
-
第二轮循环(十位相加):val1=4、val2=6,total=10,carry=1,当前值0,结果链表:0→7→0,指针后移
-
第三轮循环(百位相加):val1=3、val2=4,total=3+4+1=8,carry=0,当前值8,结果链表:0→7→0→8,指针后移
-
循环终止:两个链表遍历完成,无剩余进位
-
返回结果:dummy.next → 7→0→8,运算正确
五、C++ 完整代码实现(双解法+逐行注释+内存释放+全测试)
1. 暴力转换数字法(入门版)
cpp
#include <iostream>
#include <vector>
using namespace std;
// LeetCode官方标准链表节点结构体
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) {
long long num1 = 0, num2 = 0;
long long base = 1;
// 遍历l1,转换为完整数字
while(l1 != nullptr){
num1 += l1->val * base;
base *= 10;
l1 = l1->next;
}
// 遍历l2,转换为完整数字
base = 1;
while(l2 != nullptr){
num2 += l2->val * base;
base *= 10;
l2 = l2->next;
}
// 数字相加
long long sum = num1 + num2;
// 处理和为0的特殊情况
if(sum == 0) return new ListNode(0);
// 构建结果链表
ListNode* dummy = new ListNode(0);
ListNode* cur = dummy;
while(sum > 0){
cur->next = new ListNode(sum % 10);
sum = sum / 10;
cur = cur->next;
}
return dummy->next;
}
};
// ====================== 工具函数 ======================
// 数组创建链表
ListNode* createList(vector<int> nums){
ListNode* dummy = new ListNode(0);
ListNode* cur = dummy;
for(int num : nums){
cur->next = new ListNode(num);
cur = cur->next;
}
return dummy->next;
}
// 打印链表
void printList(ListNode* head){
ListNode* cur = head;
while(cur){
cout << cur->val;
if(cur->next) cout << "→";
cur = cur->next;
}
cout << endl;
}
// 释放链表内存(C++必备,防止内存泄漏)
void freeList(ListNode* head){
ListNode* temp;
while(head){
temp = head;
head = head->next;
delete temp;
}
}
// 自定义输入链表
ListNode* inputList(){
vector<int> nums;
int num;
cout << "请输入逆序链表数字,输入-1结束:";
while(cin >> num && num != -1){
nums.push_back(num);
}
return createList(nums);
}
// ====================== 主函数测试 ======================
int main(){
Solution sol;
// 测试用例1
cout << "【官方测试用例1】" << endl;
ListNode* l1 = createList({2,4,3});
ListNode* l2 = createList({5,6,4});
cout << "输入1:"; printList(l1);
cout << "输入2:"; printList(l2);
ListNode* res1 = sol.addTwoNumbers(l1, l2);
cout << "结果:"; printList(res1);
freeList(l1); freeList(l2); freeList(res1);
// 测试用例2
cout << "\n【官方测试用例2】" << endl;
l1 = createList({0});
l2 = createList({0});
cout << "输入1:"; printList(l1);
cout << "输入2:"; printList(l2);
ListNode* res2 = sol.addTwoNumbers(l1, l2);
cout << "结果:"; printList(res2);
freeList(l1); freeList(l2); freeList(res2);
// 测试用例3
cout << "\n【官方测试用例3】" << endl;
l1 = createList({9,9,9,9,9,9,9});
l2 = createList({9,9,9,9});
cout << "输入1:"; printList(l1);
cout << "输入2:"; printList(l2);
ListNode* res3 = sol.addTwoNumbers(l1, l2);
cout << "结果:"; printList(res3);
freeList(l1); freeList(l2); freeList(res3);
// 自定义输入
cout << "\n【自定义输入测试】" << endl;
ListNode* l1_c = inputList();
ListNode* l2_c = inputList();
ListNode* res_c = sol.addTwoNumbers(l1_c, l2_c);
cout << "自定义结果:"; printList(res_c);
freeList(l1_c); freeList(l2_c); freeList(res_c);
return 0;
}
2. 最优模拟进位法(满分AC版,逐行超详细注释)
cpp
#include <iostream>
#include <vector>
using namespace std;
// LeetCode 标准链表节点结构体
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) {
// 1. 创建虚拟哑节点,规避头节点特殊处理
ListNode* dummy = new ListNode(0);
// 2. 结果链表遍历指针,用于拼接新节点
ListNode* cur = dummy;
// 3. 进位变量,初始无进位
int carry = 0;
// 4. 核心循环:任意链表未遍历完 或 存在进位,持续执行
while(l1 != nullptr || l2 != nullptr || carry != 0){
// 空节点补0,保证长短链表统一计算逻辑
int val1 = l1 ? l1->val : 0;
int val2 = l2 ? l2->val : 0;
// 5. 计算当前位总和(含上一轮进位)
int total = val1 + val2 + carry;
// 6. 更新进位值(取十位)
carry = total / 10;
// 7. 计算当前位结果(取个位)
int curVal = total % 10;
// 8. 新建节点,拼接至结果链表
cur->next = new ListNode(curVal);
// 9. 结果指针后移
cur = cur->next;
// 10. 原链表指针后移,遍历下一位
if(l1 != nullptr) l1 = l1->next;
if(l2 != nullptr) l2 = l2->next;
}
// 11. 返回结果链表头节点(跳过虚拟哑节点)
return dummy->next;
}
};
// ====================== 工具函数 ======================
// 数组快速创建链表
ListNode* createList(vector<int> nums){
ListNode* dummy = new ListNode(0);
ListNode* cur = dummy;
for(int num : nums){
cur->next = new ListNode(num);
cur = cur->next;
}
return dummy->next;
}
// 格式化打印链表
void printList(ListNode* head){
ListNode* cur = head;
while(cur){
cout << cur->val;
if(cur->next) cout << "→";
cur = cur->next;
}
cout << endl;
}
// 手动输入创建链表
ListNode* inputList(){
vector<int> nums;
int num;
cout << "输入链表数字(空格分隔,-1结束):";
while(cin >> num && num != -1){
nums.push_back(num);
}
return createList(nums);
}
// 释放链表所有内存,杜绝内存泄漏
void freeList(ListNode* head){
ListNode* temp;
while(head != nullptr){
temp = head;
head = head->next;
delete temp;
}
}
// ====================== 主函数:全量测试 ======================
int main(){
Solution sol;
// 官方测试用例1
cout << "===== 官方测试用例1 =====" << endl;
ListNode* l1 = createList({2,4,3});
ListNode* l2 = createList({5,6,4});
cout << "输入链表1:"; printList(l1);
cout << "输入链表2:"; printList(l2);
ListNode* res1 = sol.addTwoNumbers(l1, l2);
cout << "运算结果:"; printList(res1);
freeList(l1); freeList(l2); freeList(res1);
// 官方测试用例2
cout << "\n===== 官方测试用例2 =====" << endl;
l1 = createList({0});
l2 = createList({0});
cout << "输入链表1:"; printList(l1);
cout << "输入链表2:"; printList(l2);
ListNode* res2 = sol.addTwoNumbers(l1, l2);
cout << "运算结果:"; printList(res2);
freeList(l1); freeList(l2); freeList(res2);
// 官方测试用例3
cout << "\n===== 官方测试用例3 =====" << endl;
l1 = createList({9,9,9,9,9,9,9});
l2 = createList({9,9,9,9});
cout << "输入链表1:"; printList(l1);
cout << "输入链表2:"; printList(l2);
ListNode* res3 = sol.addTwoNumbers(l1, l2);
cout << "运算结果:"; printList(res3);
freeList(l1); freeList(l2); freeList(res3);
// 自定义输入测试
cout << "\n===== 自定义输入测试 =====" << endl;
ListNode* l1_c = inputList();
ListNode* l2_c = inputList();
ListNode* res_c = sol.addTwoNumbers(l1_c, l2_c);
cout << "自定义运算结果:"; printList(res_c);
freeList(l1_c); freeList(l2_c); freeList(res_c);
return 0;
}
六、两种解法全面对比
| 对比维度 | 暴力数字转换法 | 逐位模拟进位法(最优) |
|---|---|---|
| 逻辑难度 | 极低,零基础秒懂 | 中等,需理解进位循环 |
| 数据范围 | 有限,超长链表数字溢出失效 | 无限,兼容题目所有用例 |
| 算法效率 | 两轮遍历,效率较低 | 单次遍历,时间最优 |
| 面试适用性 | 仅入门学习,面试不推荐 | 面试标准答案,满分AC |
七、算法复杂度分析
1. 时间复杂度
-
暴力法:
O(max(n,m)),两次遍历链表,n、m为两个链表长度 -
最优法:
O(max(n,m)),仅遍历最长链表一次,无冗余操作
2. 空间复杂度
两种解法均为 O(max(n,m)),结果链表长度最大为 最长链表长度+1(最终进位),无额外空间消耗。
八、全覆盖边界条件验证
-
边界1:两个单节点0链表相加 → 结果0
-
边界2:链表长度不一致(如[1,2]和[3,4,5])
-
边界3:全程连续进位(999+999)
-
边界4:最后一位产生进位(9999+1)
九、总结
-
本题核心:模拟小学竖式加法,逆序链表天然适配低位优先计算
-
核心技巧:哑节点简化链表拼接逻辑,是链表算法通用神器
-
核心关键点:循环必须包含进位判断,否则会丢失最后一位进位
-
避坑要点:禁止暴力转数字,超长链表必然溢出,面试只写模拟进位法
-
空值处理:长短链表相加,空节点统一补0,保证逻辑统一