Leetcode 111 两数相加

1 题目

2. 两数相加

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

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

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

示例 1:

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

示例 2:

复制代码
输入:l1 = [0], l2 = [0]
输出:[0]

示例 3:

复制代码
输入:l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9]
输出:[8,9,9,9,0,0,0,1]

提示:

  • 每个链表中的节点数在范围 [1, 100]
  • 0 <= Node.val <= 9
  • 题目数据保证列表表示的数字不含前导零

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) {
        ListNode* dummy = new ListNode(0);
        ListNode* cur = dummy;
        int carry = 0 ;
        while (l1 != nullptr || l2 != nullptr || carry != 0 ){
            int val1 = l1 ? l1 -> val : 0 ;
            int val2 = l2 ? l2 -> val : 0 ;
            int sum = val1 + val2 + carry  ;
            int curVal = sum % 10  ;
            carry = sum / 10 ;

            cur -> next = new ListNode(curVal);
            cur = cur -> next ;
            if (l1) l1 = l1 -> next ;
            if (l2) l2 = l2 -> next ;
        }
        ListNode* result = dummy -> next ;
        delete dummy ;
        return result ;
    }
};

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 dummy = new ListNode(0);
    let cur = dummy ;
    let carry = 0 ;

    while(l1!= null || l2 != null || carry != 0 ){
        const val1 = l1 ? l1.val : 0 ;
        const val2 = l2 ? l2.val : 0 ;
        const sum = val1 + val2 + carry ;
        carry = Math.floor(sum / 10) ;
        let curVal = sum % 10 ;

        cur.next = new ListNode(curVal);
        cur = cur.next ;
        if(l1) l1 = l1.next ;
        if(l2) l2 = l2.next ;  
    }
    res = dummy.next ;
    return res;
};

思考

我的玛雅,这什么啊,我一看感觉就要存三个变量,list1 中节点的val ,list2 中节点的val ,两个val相加的进位,需要取余操作吧,然后这个进位又要和下一个相加。

尝试用cpp写一遍,写好睡觉。。写不来,是要一直在算一个val吗?好奇怪!!!我做的是一直创建一个节点,录入这个节点的值的工作吗?

但是我不知道具体代码怎么写。。唉

题解(c++)

你对题目的核心思路判断完全正确!核心就是逐位取两个链表的节点值 + 进位值,通过取余得到当前位结果、整除得到新的进位,再依次创建新节点串联成结果链表,这就是本题的核心解法。下面一步步帮你把思路落地成可运行的 C++ 代码,同时讲清所有关键细节。

一、先定义链表节点结构

LeetCode 中该题的链表节点定义是固定的,先定义好基础结构:

cpp 复制代码
// 链表节点定义(题目给定)
struct ListNode {
    int val;        // 存储当前位数字(0-9)
    ListNode *next; // 指向下一个节点的指针
    // 构造函数:方便创建节点(直接传值,next默认空)
    ListNode() : val(0), next(nullptr) {}
    ListNode(int x) : val(x), next(nullptr) {}
    ListNode(int x, ListNode *next) : val(x), next(next) {}
};

二、核心解题代码(完整可运行)

采用迭代法实现(最直观、效率最高,时间复杂度 O (max (n,m)),n/m 是两个链表的长度),代码带详细注释,完全贴合你的思路:

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

// 先粘贴上面的ListNode定义

class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        ListNode* dummy = new ListNode(0); // 哑节点(哨兵节点),简化头节点处理
        ListNode* cur = dummy;             // 遍历指针,用于创建新节点
        int carry = 0;                     // 进位值,初始为0(无进位)

        // 循环条件:l1没遍历完 OR l2没遍历完 OR 还有进位(三个条件满足一个就继续)
        while (l1 != nullptr || l2 != nullptr || carry != 0) {
            // 取l1当前节点值:不为空则取val,否则取0(链表遍历完的补0操作)
            int val1 = l1 ? l1->val : 0;
            // 取l2当前节点值:同理
            int val2 = l2 ? l2->val : 0;

            // 核心计算:当前位总和 = l1值 + l2值 + 上一位进位
            int sum = val1 + val2 + carry;
            // 当前位结果:总和对10取余(比如sum=10,当前位是0)
            int curVal = sum % 10;
            // 新的进位:总和除以10(整数除法,比如sum=10,进位是1)
            carry = sum / 10;

            // 创建新节点存储当前位结果,挂到cur后面
            cur->next = new ListNode(curVal);
            cur = cur->next; // cur后移,准备创建下一个节点

            // l1、l2非空则后移,空则保持null(后续补0)
            if (l1) l1 = l1->next;
            if (l2) l2 = l2->next;
        }

        // 结果链表是哑节点的下一个节点(跳过初始的0节点)
        ListNode* result = dummy->next;
        delete dummy; // 释放哑节点内存,避免内存泄漏
        return result;
    }
};

// 测试代码:辅助函数(创建链表、打印链表)
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) {
        cout << cur->val;
        if (cur->next) cout << " -> ";
        cur = cur->next;
    }
    cout << endl;
}

// 主函数测试
int main() {
    // 示例1:l1=[2,4,3], l2=[5,6,4] → 输出[7,0,8]
    int arr1[] = {2,4,3};
    int arr2[] = {5,6,4};
    ListNode* l1 = createList(arr1, 3);
    ListNode* l2 = createList(arr2, 3);

    Solution sol;
    ListNode* res = sol.addTwoNumbers(l1, l2);
    cout << "示例1结果:";
    printList(res);

    // 示例3:l1=[9,9,9,9,9,9,9], l2=[9,9,9,9] → 输出[8,9,9,9,0,0,0,1]
    int arr3[] = {9,9,9,9,9,9,9};
    int arr4[] = {9,9,9,9};
    ListNode* l3 = createList(arr3, 7);
    ListNode* l4 = createList(arr4, 4);
    ListNode* res2 = sol.addTwoNumbers(l3, l4);
    cout << "示例3结果:";
    printList(res2);

    return 0;
}

三、核心思路拆解(完全贴合你的想法)

你想到的三个核心变量(l1.val、l2.val、进位)、取余操作都是本题的关键,这里把思路理得更清晰:

  1. 哑节点(dummy):解决「结果链表头节点不好处理」的问题,不用单独判断头节点是否为空,直接在哑节点后挂新节点即可;
  2. 逐位计算 :循环遍历两个链表,每次取当前节点值(空则补 0)+ 进位,计算总和;
  3. 取余 + 整除
    • 总和 % 10 → 得到当前位的数字(存入新节点);
    • 总和 / 10 → 得到下一位的进位(整数除法,0 或 1,因为两个个位数 + 进位最大是 9+9+1=19);
  4. 循环终止条件:必须同时满足「l1 遍历完、l2 遍历完、进位为 0」,否则会漏掉最后一位的进位(比如示例 3,最后进位 1 需要单独创建节点);
  5. 节点创建:每次计算完当前位,就创建一个新节点,挂到结果链表的末尾,这就是你说的「一直创建节点、录入节点值」的工作,是本题的核心操作。

四、代码运行结果

复制代码
示例1结果:7 -> 0 -> 8
示例3结果:8 -> 9 -> 9 -> 9 -> 0 -> 0 -> 0 -> 1

完全匹配题目示例要求。

五、关键细节提醒

  1. 链表补 0:当其中一个链表遍历完时,后续的节点值按 0 处理,避免空指针访问;
  2. 最后进位 :循环结束条件必须包含carry != 0,否则像999 + 999 = 1998这样的用例,最后一位的进位 1 会丢失;
  3. 内存释放:哑节点是临时创建的,最后要手动释放,避免 C++ 中的内存泄漏;
  4. 指针后移:l1、l2、cur 指针只有在非空时才后移,否则保持 null。

总结

  1. 核心解法:逐位求和(l1.val + l2.val + 进位)→ 取余存当前位 → 整除更新进位 → 循环创建新节点
  2. 关键技巧:用哑节点简化结果链表的头节点处理,避免繁琐的空判断;
  3. 循环条件:l1 != nullptr || l2 != nullptr || carry != 0(三个条件缺一不可);
  4. 你的初始思路完全正确,只是需要把「存三个变量、取余、创建节点」的步骤用循环串联起来,这就是本题的完整解法。

自己写的错误百出的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 dummy = new ListNode(0);
    cur = dummy ;
    let carry = 0 ;

    while(l1!= null || l2 != null || carry != 0 ){
        let val1 = l1 ? l1.val : 0 ;
        let val2 = l2 ? l2.val : 0 ;
        let sum = val1 + val2 + carry ;
        carry = sum / 10 ;
        let curVal = sum % 10 ;

        cur.next = new ListNode(curVal);
        cur = cur.next ;
        if(l1) l1 = l1.next ;
        if(l2) l2 = l2.next ;  
    }
    res = dummy.next ;
    return res;
};

你这份 JavaScript 代码的核心解题逻辑完全正确 ,仅存在2 个未修复的关键问题(也是之前提到的致命错误),导致代码无法正确运行,修复后即可在 LeetCode 直接提交通过。下面先明确剩余问题,再给出最终可运行代码,同时补充 JS 专属的优化细节~

一、当前代码仍存在的 2 个致命问题(必须修复)

问题 1:变量curres未用let/const声明,形成隐式全局变量

JavaScript 中未显式声明的变量会挂载到全局作用域,不仅容易和其他代码冲突,在严格模式下会直接抛出未定义变量错误,这是语法层面的硬性问题。

javascript 复制代码
cur = dummy ; // 未声明,错误
// 正确:let cur = dummy;
res = dummy.next ; // 未声明,错误
// 正确:const res = dummy.next;
问题 2:进位计算用普通除法/,返回浮点数导致逻辑错误

两个个位数 + 进位的最大值为 9+9+1=19,进位只能是整数 0 或 1 。普通除法/会返回浮点数(比如sum=9时,9/10=0.9),浮点数参与下一次求和会导致计算偏差,必须用Math.floor()取整数商。

javascript 复制代码
carry = sum / 10 ; // 浮点数进位,错误
// 正确:carry = Math.floor(sum / 10);

二、最终可直接提交的完整修正代码(带详细注释)

修复所有错误 + 优化代码规范,兼容题目所有测试用例(示例 1/2/3 均能正确输出):

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) {
    // 1. 创建哑节点(哨兵节点),简化头节点处理,必须加new
    const dummy = new ListNode(0);
    // 2. 显式声明遍历指针,块级作用域,避免全局变量
    let cur = dummy;
    // 3. 进位初始值为0,无进位
    let carry = 0;

    // 循环条件:l1未遍历完 || l2未遍历完 || 还有进位(三者满足其一就继续)
    while (l1 !== null || l2 !== null || carry !== 0) {
        // 取l1当前值:非空取val,空则补0(避免空指针访问)
        const val1 = l1 ? l1.val : 0;
        // 取l2当前值:同上
        const val2 = l2 ? l2.val : 0;
        // 核心计算:当前位总和 = l1值 + l2值 + 上一位进位
        const sum = val1 + val2 + carry;
        // 更新进位:取整数商,保证进位是0/1(关键修复)
        carry = Math.floor(sum / 10);
        // 当前位结果:总和对10取余,得到0-9的有效数字
        const curVal = sum % 10;

        // 创建新节点存储当前位结果,挂到cur后(必须加new)
        cur.next = new ListNode(curVal);
        // cur后移,准备创建下一个节点
        cur = cur.next;
        // l1非空则后移,空则保持null(后续补0)
        if (l1) l1 = l1.next;
        // l2非空则后移,空则保持null(后续补0)
        if (l2) l2 = l2.next;
    }

    // 显式声明结果变量,指向哑节点下一个节点(有效结果头节点,关键修复)
    const res = dummy.next;
    // 返回结果链表
    return res;
};

三、额外优化的代码规范点(提升可读性,非必须但推荐)

  1. 空判断从!= null改为!== null:使用严格相等运算符,避免 JavaScript 隐式类型转换,代码逻辑更清晰;
  2. 局部变量用const声明:val1/val2/sum/curVal/res这些后续不修改的变量,用const替代let,符合 JS 编程规范,明确变量不可变;
  3. 统一语句结尾分号:避免 JavaScript 自动分号插入(ASI)机制带来的语法歧义,这是通用的前端编程规范。

四、测试用例验证(和题目要求完全一致)

  1. 示例 1:l1=[2,4,3]l2=[5,6,4] → 输出[7,0,8](342+465=807);
  2. 示例 2:l1=[0]l2=[0] → 输出[0](0+0=0);
  3. 示例 3:l1=[9,9,9,9,9,9,9]l2=[9,9,9,9] → 输出[8,9,9,9,0,0,0,1](9999999+9999=10009998)。

核心总结

你这份代码的解题思路毫无问题 ,剩余问题均集中在JavaScript 基础语法细节(变量声明、整数除法),这是从其他语言转写 JS 时的常见踩坑点,并非逻辑问题。

3 小结

思路很丝滑,每天多练多写,然后最后找个模块回头来小结一下。算法题的进度真的要加快了,加油。。!!

相关推荐
摇滚侠2 小时前
【程序员入门系列】jQuery 零基础入门到精通!Jquery 选择器 API
前端·javascript·jquery
TracyCoder1232 小时前
LeetCode Hot100(21/100)——234. 回文链表
算法·leetcode·链表
驭渊的小故事2 小时前
类和对象的笔记3
笔记
可涵不会debug2 小时前
Redis魔法学院——第四课:哈希(Hash)深度解析:Field-Value 层级结构、原子性操作与内部编码优化
数据库·redis·算法·缓存·哈希算法
@––––––2 小时前
力扣hot100—系列1
算法·leetcode·职场和发展
老鼠只爱大米2 小时前
LeetCode经典算法面试题 #236:二叉树的最近公共祖先(RMQ转化、Tarjan离线算法等五种实现方案详细解析)
算法·leetcode·二叉树·lca·并查集·最近公共祖先·rmq
问好眼2 小时前
【信息学奥赛一本通】1296:开餐馆
c++·算法·动态规划·信息学奥赛
yxm26336690812 小时前
【洛谷压缩技术续集题解】
java·开发语言·算法
四谎真好看2 小时前
JavaWeb学习笔记(Day12)
笔记·学习·学习笔记·javaweb