1 题目
给你两个 非空链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。
你可以假设除了数字 0 之外,这两个数字都不会以零开头。
示例1:

输入:l1 = [7,2,4,3], l2 = [5,6,4]
输出:[7,8,0,7]
示例2:
输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[8,0,7]
示例3:
输入:l1 = [0], l2 = [0]
输出:[0]
提示:
- 链表的长度范围为
[1, 100] 0 <= node.val <= 9- 输入数据保证链表代表的数字无前导 0
2 代码实现
c++
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) {
stack<int> s1 , s2 ;
while (l1 != nullptr){
s1.push (l1 -> val );
l1 = l1 -> next ;
}
while (l2 != nullptr){
s2.push (l2 -> val);
l2 = l2 -> next ;
}
int carry = 0 ;
ListNode* head = nullptr ;
while (!s1.empty() || !s2.empty() || carry != 0 ){
int num1 = 0 , num2 = 0 ;
if (!s1.empty()){
num1 = s1.top ();
s1.pop();
}
if(!s2.empty()){
num2 = s2.top();
s2.pop();
}
int sum = num1 + num2 + carry ;
carry = sum / 10 ;
int curVal = sum % 10 ;
ListNode* newNode = new ListNode(curVal);
newNode -> next = head ;
head =newNode ;
}
return head ;
}
};
js
javascript
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} l1
* @param {ListNode} l2
* @return {ListNode}
*/
var addTwoNumbers = function(l1, l2) {
const s1 =[] ,s2 = [];
while(l1){
s1.push(l1.val);
l1 = l1.next ;
}
while (l2){
s2.push(l2.val);
l2 = l2.next ;
}
let carry = 0 ;
let head = null ;
while (s1.length > 0 || s2.length > 0 || carry > 0){
const num1 = s1.length > 0 ? s1.pop() : 0 ;
const num2 = s2.length > 0 ? s2.pop() : 0 ;
const sum = num1 + num2 + carry ;
carry = Math.floor(sum / 10 );
const curVal = sum % 10 ;
const newNode = new ListNode (curVal);
newNode.next = head ;
head = newNode ;
}
return head ;
};
思考
我觉得有点难啊,怎么搞的,不清楚啊,和上次做的题目的唯一区别就头结点是低位 / 高位 ;
怎么搞的。
Leetcode 111 两数相加-CSDN博客 不知道怎么写了。
题解
你觉得这道题难,核心原因是数字最高位在链表头 ,无法像「两数相加 I」那样从低位(链表尾)开始逐位相加,而链表又不能直接反向遍历,所以需要先解决低位访问的问题,栈是处理这个问题的最优选择(先进后出特性完美匹配 "从尾到头取数")。
解题核心思路
- 栈存储数字 :将两个链表的所有节点值分别压入两个栈,此时栈顶为数字的最低位,实现 "从低位到高位" 的访问;
- 逐位相加 :从两个栈顶同时取数,计算当前位和(含进位),当前位值为
和%10,新进位为和/10; - 头插法建链表 :由于相加结果是从低位到高位计算的,需要用头插法将每个计算结果节点插入新链表头部,保证最终链表头为最高位;
- 处理剩余进位:若所有数相加后仍有进位(如 999 + 1 = 1000),需在链表头部补充进位节点。
完整 C++ 代码实现
cpp
#include <iostream>
#include <stack>
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) {
stack<int> s1, s2;
// 步骤1:将两个链表的数字分别压入栈,栈顶为最低位
while (l1 != nullptr) {
s1.push(l1->val);
l1 = l1->next;
}
while (l2 != nullptr) {
s2.push(l2->val);
l2 = l2->next;
}
int carry = 0; // 进位,初始为0
ListNode* head = nullptr; // 结果链表头结点,初始为空
// 步骤2:逐位相加,直到栈空且无进位
while (!s1.empty() || !s2.empty() || carry != 0) {
int num1 = 0, num2 = 0;
if (!s1.empty()) {
num1 = s1.top();
s1.pop();
}
if (!s2.empty()) {
num2 = s2.top();
s2.pop();
}
int sum = num1 + num2 + carry; // 当前位总和(含进位)
carry = sum / 10; // 新的进位
int curVal = sum % 10; // 当前位的数值
// 步骤3:头插法创建新节点,保证高位在前
ListNode* newNode = new ListNode(curVal);
newNode->next = head;
head = newNode;
}
return head;
}
};
// 辅助函数:创建链表(用于测试)
ListNode* createList(int arr[], int n) {
if (n == 0) return nullptr;
ListNode* head = new ListNode(arr[0]);
ListNode* cur = head;
for (int i = 1; i < n; i++) {
cur->next = new ListNode(arr[i]);
cur = cur->next;
}
return head;
}
// 辅助函数:打印链表(用于测试)
void printList(ListNode* head) {
ListNode* cur = head;
while (cur != nullptr) {
cout << cur->val;
if (cur->next != nullptr) cout << " -> ";
cur = cur->next;
}
cout << endl;
}
// 测试主函数
int main() {
// 示例1:l1 = [7,2,4,3], l2 = [5,6,4],输出[7,8,0,7]
int arr1[] = {7,2,4,3};
int arr2[] = {5,6,4};
ListNode* l1 = createList(arr1, 4);
ListNode* l2 = createList(arr2, 3);
Solution sol;
ListNode* res = sol.addTwoNumbers(l1, l2);
cout << "相加结果:";
printList(res);
return 0;
}
关键细节解析
1. 栈的作用
将链表 7->2->4->3 压入栈后,栈内元素为 [7,2,4,3](栈底到栈顶),栈顶是 3(最低位) ,出栈顺序为 3→4→2→7,刚好匹配加法 "从低位到高位" 的计算顺序。
2. 头插法的必要性
假设相加过程得到的位依次是 7→0→8→7(低位到高位),如果用普通尾插法,链表会是 7->0->8->7(低位在前),不符合题目要求;头插法 会依次将节点插入头部,最终得到 7->8->0->7(高位在前),完美匹配结果要求。
3. 循环条件的完整性
循环条件为 !s1.empty() || !s2.empty() || carry != 0,覆盖三种情况:
- 栈 s1 还有数未加;
- 栈 s2 还有数未加;
- 所有数都加完,但仍有进位(如
999 + 1 = 1000,最后需要补充进位 1)。
4. 与「两数相加 I」的核心区别
| 特性 | 两数相加 I | 两数相加 II |
|---|---|---|
| 数字存储顺序 | 低位在链表头 | 高位在链表头 |
| 遍历顺序 | 从链表头直接遍历(低位→高位) | 借助栈反向遍历(低位→高位) |
| 链表构建方式 | 尾插法 | 头插法 |
代码运行结果
以示例 1 为例,输入 l1 = [7,2,4,3]、l2 = [5,6,4],运行代码后输出:
bash
相加结果:7 -> 8 -> 0 -> 7
完全匹配题目要求。
复杂度分析
- 时间复杂度:O(m+n),其中 、 分别是两个链表的长度,需遍历两个链表压栈(O(m+n)),再遍历栈相加(O(m+n)),总时间为线性。
- 空间复杂度:O(m+n),主要为两个栈的存储空间,最坏情况下存储所有节点值。
测试用例扩展
- 示例 2:
l1 = [2,4,3],l2 = [5,6,4]→ 输出[8,0,7]; - 示例 3:
l1 = [0],l2 = [0]→ 输出[0]; - 有进位:
l1 = [9,9],l2 = [1]→ 输出[1,0,0]。
只需修改主函数中的数组和长度,即可测试上述用例。
js实现
其实我不知js应该如何写这个语法,感觉有点难啊。js怎么写栈,做这个题目效率奇低。。
两数相加 II(JavaScript 实现)
你觉得这道题用 JavaScript 实现难,核心是对JS 中栈的模拟 、链表节点的创建 / 操作 不熟悉,再加上需要结合头插法建链表的逻辑,所以会觉得效率低、无从下手。
其实 JS 中用数组就能完美模拟栈(先进后出),链表操作也有固定范式,下面我会从核心思路 、JS 栈的实现 、完整代码 、关键细节四个部分讲清楚,保证易懂且效率达标。
一、核心解题思路(和 C++ 完全一致)
这道题和「两数相加 I」的核心区别是数字高位在链表头,无法直接从低位开始相加,所以核心解决思路不变:
- 栈存数字 :将两个链表的节点值分别存入两个 "栈",利用栈先进后出 特性,让栈顶为数字最低位,实现从低位到高位的访问;
- 逐位相加:从两个栈顶取数,计算当前位和(含进位),当前位值 = 和 % 10,新进位 = 和 / 10(取整);
- 头插法建链表 :相加结果是从低位到高位计算的,用头插法将每个结果节点插入新链表头部,保证最终链表高位在前;
- 处理剩余进位:所有数相加完若还有进位(如 999 + 1 = 1000),需在链表头部补充进位节点。
二、JavaScript 中栈的实现(极简高效)
JS 中没有原生的 Stack 类,但数组的 push()(入栈)和 pop()(出栈,取栈顶元素) 方法完全符合栈的先进后出特性,是模拟栈的最优选择,时间复杂度均为 O (1),效率和原生栈一致。
- 入栈:
stack.push(val)→ 向数组末尾添加元素(栈顶); - 出栈 / 取栈顶:先通过
stack[stack.length - 1]取栈顶值,再用stack.pop()移除栈顶元素; - 判断栈空:
stack.length === 0。
三、JavaScript 完整实现(含注释 + 测试用例)
3.1 完整代码(兼容 LeetCode 提交)
LeetCode 中 JS 提交时,链表节点会被自动定义,直接写核心逻辑即可,代码如下:
javascript
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} l1
* @param {ListNode} l2
* @return {ListNode}
*/
var addTwoNumbers = function(l1, l2) {
// 用数组模拟两个栈,存储链表节点值
const s1 = [], s2 = [];
// 步骤1:将l1所有节点值压入栈s1
while (l1) {
s1.push(l1.val);
l1 = l1.next;
}
// 步骤1:将l2所有节点值压入栈s2
while (l2) {
s2.push(l2.val);
l2 = l2.next;
}
let carry = 0; // 进位,初始为0
let head = null; // 结果链表头节点,初始为null
// 步骤2:逐位相加,直到栈空且无进位(核心循环)
while (s1.length > 0 || s2.length > 0 || carry > 0) {
// 取栈顶值,栈空则为0
const num1 = s1.length > 0 ? s1.pop() : 0;
const num2 = s2.length > 0 ? s2.pop() : 0;
const sum = num1 + num2 + carry; // 当前位总和(含进位)
carry = Math.floor(sum / 10); // 计算新进位(取整,避免小数)
const curVal = sum % 10; // 当前位的最终值
// 步骤3:头插法创建新节点,保证高位在前
const newNode = new ListNode(curVal);
newNode.next = head; // 新节点的next指向当前头节点
head = newNode; // 更新头节点为新节点
}
return head;
};
3.2 本地测试代码(可直接运行)
如果想在本地浏览器 / Node.js 中测试,需要手动实现链表创建 、链表打印辅助函数,完整可运行代码如下:
javascript
// 链表节点构造函数
function ListNode(val, next) {
this.val = (val === undefined ? 0 : val);
this.next = (next === undefined ? null : next);
}
// 核心解题函数
var addTwoNumbers = function(l1, l2) {
const s1 = [], s2 = [];
while (l1) {
s1.push(l1.val);
l1 = l1.next;
}
while (l2) {
s2.push(l2.val);
l2 = l2.next;
}
let carry = 0;
let head = null;
while (s1.length > 0 || s2.length > 0 || carry > 0) {
const num1 = s1.length > 0 ? s1.pop() : 0;
const num2 = s2.length > 0 ? s2.pop() : 0;
const sum = num1 + num2 + carry;
carry = Math.floor(sum / 10);
const curVal = sum % 10;
const newNode = new ListNode(curVal);
newNode.next = head;
head = newNode;
}
return head;
};
// 辅助函数:根据数组创建链表(如 [7,2,4,3] → 7→2→4→3)
function createList(arr) {
if (arr.length === 0) return null;
const head = new ListNode(arr[0]);
let cur = head;
for (let i = 1; i < arr.length; i++) {
cur.next = new ListNode(arr[i]);
cur = cur.next;
}
return head;
}
// 辅助函数:打印链表(如 7→8→0→7)
function printList(head) {
const res = [];
let cur = head;
while (cur) {
res.push(cur.val);
cur = cur.next;
}
console.log(res.join(' -> '));
}
// 测试用例1:l1=[7,2,4,3], l2=[5,6,4] → 输出7->8->0->7
const l1 = createList([7,2,4,3]);
const l2 = createList([5,6,4]);
printList(addTwoNumbers(l1, l2));
// 测试用例2:l1=[2,4,3], l2=[5,6,4] → 输出8->0->7
// const l1 = createList([2,4,3]);
// const l2 = createList([5,6,4]);
// printList(addTwoNumbers(l1, l2));
// 测试用例3:l1=[0], l2=[0] → 输出0
// const l1 = createList([0]);
// const l2 = createList([0]);
// printList(addTwoNumbers(l1, l2));
// 测试用例4:l1=[9,9], l2=[1] → 输出1->0->0
// const l1 = createList([9,9]);
// const l2 = createList([1]);
// printList(addTwoNumbers(l1, l2));
四、JS 实现的关键细节解析(避坑 + 理解)
4.1 栈的操作细节
JS 数组模拟栈时,不要用 shift ()/unshift () (这两个方法是操作数组头部,时间复杂度 O (n),会降低效率),只用 push()(入栈)和 pop()(出栈),这是保证效率的关键。
4.2 进位的处理
JS 中除法 / 会返回小数(如 17/10 = 1.7),所以计算进位时必须用 Math.floor(sum / 10) 取整,否则进位会是小数,导致后续计算错误。
4.3 头插法的核心逻辑(必懂)
因为我们是从低位到高位 计算结果(比如先算个位,再算十位,最后算百位),而题目要求结果链表高位在前(百位→十位→个位),所以必须用头插法:
- 初始时结果链表头节点
head = null; - 每计算一个低位值,创建新节点,让新节点的
next指向当前的head; - 再将
head更新为这个新节点; - 最终
head就是高位在前的结果链表头。
举例:计算 7243 + 564 = 7807,计算顺序是 3+4=7(个位)→4+6=10(十位,留 0 进 1)→2+5+1=8(百位)→7+0=7(千位),头插法过程:
- 个位 7:newNode (7) → next=null → head=7
- 十位 0:newNode (0) → next=7 → head=0→7
- 百位 8:newNode (8) → next=0→7 → head=8→0→7
- 千位 7:newNode (7) → next=8→0→7 → head=7→8→0→7(最终结果)
4.4 循环条件的完整性
循环条件 s1.length > 0 || s2.length > 0 || carry > 0 必须包含三个条件,缺一不可:
s1.length > 0:s1 栈还有数未加;s2.length > 0:s2 栈还有数未加;carry > 0:所有数加完但还有进位(如 999+1=1000,最后需要补充进位 1)。
4.5 链表节点的判空
JS 中链表节点判空直接用 if (l1) 即可(等价于 if (l1 !== null && l1 !== undefined)),比写 l1 !== null 更简洁。
五、复杂度分析(JS 实现和 C++ 效率一致)
- 时间复杂度:O (m + n),其中 m、n 分别是两个链表的长度。需要遍历两个链表压栈(O (m+n)),再遍历栈逐位相加(O (m+n)),总时间为线性,无额外耗时;
- 空间复杂度:O (m + n),主要为两个栈的存储空间,最坏情况下需要存储两个链表的所有节点值。
六、和 C++ 实现的异同
| 特性 | C++ 实现 | JavaScript 实现 |
|---|---|---|
| 栈的实现 | 原生 stack<int> 类 |
数组 [](push/pop) |
| 链表节点创建 | new ListNode(curVal) |
new ListNode(curVal) |
| 头插法逻辑 | 完全一致 | 完全一致 |
| 进位计算 | 整数除法 sum / 10 |
Math.floor(sum / 10) |
| 判空方式 | l1 != nullptr |
if (l1) |
| 时间 / 空间复杂度 | O(m+n) | O(m+n) |
简单说:核心逻辑完全相同,只是语法细节适配了各自语言,JS 实现的效率和 C++ 基本一致,不存在 "效率奇低" 的问题。
总结
用 JS 解这道题的核心要点就 3 个:
- 用数组模拟栈:只用 push/pop 方法,保证效率;
- 头插法建链表:解决 "低位计算、高位在前" 的问题;
- 处理好进位:用 Math.floor 取整,循环条件包含剩余进位。
你之前觉得难,只是对 JS 的栈模拟和链表操作不熟悉,多写两遍测试用例,熟悉这些固定范式后,就会发现这道题的 JS 实现其实很简洁。
3 小结
问题不过夜,是这样的,现在回头看看昨天晚上卡住的题目,无非就是核心数据结构栈操作而已,但是却不太会做,现在自己做也有点效率低,先收藏一下。
主要就是1.栈操作 , 2.头插法
算法题可以在调节写项目或者睡前 / 早上,这些时候状态最好头脑最清晰,睡前还可以反思一下。下次不这么乱七八糟磨洋工了。