如何高效求解逆序链表表示的两数之和?详解LeetCode算法实现
本文针对LeetCode经典题目「两数相加」展开分析,详细讲解基于单向链表的数字求和技巧。通过虚拟头节点和进位优化策略,实现时间复杂度O(n)的高效解法。
1. 题目链接
2. 题目描述
给定两个非空链表,分别表示两个非负整数。每个节点存储一位数字,且按逆序方式存储(例如:数字123存储为3→2→1)。要求将这两个数相加,并以相同形式的链表返回结果。
示例说明
输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807,逆序存储为708 → 输出链表7→0→8
3. 示例分析
示例1:等长链表且有进位
输入:
l1: 9→9→9(对应数字999)
l2: 1→0→0→1(对应数字1001)
输出:0→0→0→2
计算过程:999 + 1001 = 2000 → 逆序链表0→0→0→2
关键点:
- 链表长度不一致时的处理
- 最终进位生成新节点
示例2:长度不同且末位进位
输入:
l1: 9→9(99)
l2: 9→9→9→9(9999)
输出:8→9→0→0→1
计算过程:99 + 9999 = 10098 → 逆序存储为89001
4. 算法思路
核心思想:模拟竖式加法
- 同步遍历:同时遍历两个链表的当前节点。
- 进位处理:计算当前位之和时累加前一位的进位。
- 虚拟头节点:简化链表构造,避免头节点特殊处理。
- 循环终止条件:两链表均遍历完且无剩余进位。
步骤拆解:
- 初始化虚拟头节点
newhead
和尾指针prev
。 - 使用变量
t
累计当前位的总和(包括进位)。 - 循环处理每个节点:
- 累加当前节点值到
t
(若存在) - 计算当前位的值
t % 10
- 更新进位
t = t / 10
- 累加当前节点值到
- 最终返回虚拟头节点的下一个节点。
5. 边界条件与注意事项
关键边界条件
- 链表长度不同:当其中一个链表遍历完时,继续处理另一个链表剩余节点。
- 最高位进位:循环结束后若
t > 0
,需创建新节点存储进位值。 - 全零情况:输入为0+0时,需保证输出正确(单个节点0)。
注意事项
- 内存管理:创建的虚拟头节点需手动释放,避免内存泄漏。
- 指针操作:移动
cur1
和cur2
时需先取值再后移,防止空指针异常。 - 进位更新顺序:必须先计算当前位值再更新进位,否则逻辑错误。
6. 代码实现
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode* cur1 = l1, *cur2 = l2;
ListNode* newhead = new ListNode(0); // 虚拟头节点简化操作
ListNode* prev = newhead; // 尾插法构建结果链表
int t = 0; // 同时记录当前位和及进位
while (cur1 || cur2 || t) { // 任一链表未结束或存在进位时继续
// 处理当前节点值
if (cur1) {
t += cur1->val;
cur1 = cur1->next;
}
if (cur2) {
t += cur2->val;
cur2 = cur2->next;
}
// 创建新节点并更新指针
prev->next = new ListNode(t % 10);
prev = prev->next;
t /= 10; // 计算进位
}
// 释放虚拟头节点内存
ListNode* res = newhead->next;
delete newhead;
return res;
}
};
代码解析
- 虚拟头节点:
newhead
作为占位节点,其next
指向结果链表的真实头节点。 - 循环条件:
cur1 || cur2 || t
确保处理所有可能情况。 - 进位复用:变量
t
先累加节点值,再计算当前位和进位,优化空间复杂度至O(1)。
总结
本算法通过逐位相加和进位处理,高效解决了逆序链表求和问题。重点在于正确处理链表长度差异及进位生成新节点的情况。掌握虚拟头节点技巧可显著简化链表操作,提升代码可读性和健壮性。