(LeetCode-Hot100)2. 两数相加

2. 两数相加

LeetCode 题目链接

❌|✅|💡|📌 问题简介

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

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

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

题目描述

  • 输入:两个单链表 l1l2
  • 输出:一个新的单链表,表示 l1l2 所代表数字之和
  • 约束:
    • 每个链表节点值为 0-9
    • 链表按逆序存储(个位在头)
    • 不会以 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. 初始化

    • 创建一个虚拟头节点 dummy,方便处理结果链表
    • 设置指针 curr 指向当前要处理的位置
    • 设置进位 carry = 0
  2. 循环处理

    • l1l2 还有节点,或者还有进位时,继续循环
    • 获取当前位的值:如果链表还有节点就取值,否则为 0
    • 计算当前位的和:sum = val1 + val2 + carry
    • 更新进位:carry = sum / 10
    • 创建新节点:sum % 10
    • 移动指针到下一位
  3. 返回结果

    • 返回 dummy.next(跳过虚拟头节点)

方法二:递归解法

也可以使用递归来解决这个问题:

  1. 基础情况:如果两个链表都为空且没有进位,返回 null
  2. 递归情况
    • 计算当前位的和
    • 创建当前节点
    • 递归处理剩余部分
    • 连接当前节点和递归结果

方法三:先转换为数字再相加(不推荐)

理论上可以将链表转换为整数,相加后再转回链表,但这种方法有以下问题:

  • 可能导致整数溢出(题目中链表可能很长)
  • 违背了链表操作的本质
  • 时间复杂度更高

因此不推荐使用方法三。

💻 代码实现

=== "Java"

java 复制代码
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        ListNode dummy = new ListNode(0);
        ListNode curr = dummy;
        int carry = 0;
        
        while (l1 != null || l2 != null || carry != 0) {
            int val1 = (l1 != null) ? l1.val : 0;
            int val2 = (l2 != null) ? l2.val : 0;
            
            int sum = val1 + val2 + carry;
            carry = sum / 10;
            curr.next = new ListNode(sum % 10);
            
            curr = curr.next;
            if (l1 != null) l1 = l1.next;
            if (l2 != null) l2 = l2.next;
        }
        
        return dummy.next;
    }
}

=== "Go"

go 复制代码
/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */
func addTwoNumbers(l1 *ListNode, l2 *ListNode) *ListNode {
    dummy := &ListNode{Val: 0}
    curr := dummy
    carry := 0
    
    for l1 != nil || l2 != nil || carry != 0 {
        val1 := 0
        if l1 != nil {
            val1 = l1.Val
            l1 = l1.Next
        }
        
        val2 := 0
        if l2 != nil {
            val2 = l2.Val
            l2 = l2.Next
        }
        
        sum := val1 + val2 + carry
        carry = sum / 10
        curr.Next = &ListNode{Val: sum % 10}
        curr = curr.Next
    }
    
    return dummy.Next
}

📊 示例演示

让我们用示例 1 来演示算法执行过程:

复制代码
l1 = [2,4,3]  // 表示 342
l2 = [5,6,4]  // 表示 465

步骤 1: 2 + 5 + 0 = 7, carry = 0 → [7]
步骤 2: 4 + 6 + 0 = 10, carry = 1 → [7,0]
步骤 3: 3 + 4 + 1 = 8, carry = 0 → [7,0,8]
步骤 4: 两个链表都结束,carry = 0,结束

结果: [7,0,8]  // 表示 807

对于示例 3 的演示:

复制代码
l1 = [9,9,9,9,9,9,9]
l2 = [9,9,9,9]

步骤 1: 9 + 9 + 0 = 18, carry = 1 → [8]
步骤 2: 9 + 9 + 1 = 19, carry = 1 → [8,9]
步骤 3: 9 + 9 + 1 = 19, carry = 1 → [8,9,9]
步骤 4: 9 + 9 + 1 = 19, carry = 1 → [8,9,9,9]
步骤 5: 9 + 0 + 1 = 10, carry = 1 → [8,9,9,9,0]
步骤 6: 9 + 0 + 1 = 10, carry = 1 → [8,9,9,9,0,0]
步骤 7: 9 + 0 + 1 = 10, carry = 1 → [8,9,9,9,0,0,0]
步骤 8: 0 + 0 + 1 = 1, carry = 0 → [8,9,9,9,0,0,0,1]

✅ 答案有效性证明

我们的解法是正确的,原因如下:

  1. 正确处理进位:每次计算都考虑了上一位的进位,并正确计算当前位的进位
  2. 处理不同长度:当一个链表比另一个短时,我们将其后续位视为 0
  3. 处理最终进位:即使两个链表都处理完了,如果有进位仍会创建新节点
  4. 保持逆序:由于输入是逆序的,我们按顺序处理每一位,结果自然也是逆序的

数学归纳法证明

  • 基础情况:当两个链表都为空且无进位时,返回空链表,正确
  • 归纳假设:假设前 k 位计算正确
  • 归纳步骤:第 k+1 位的计算基于前 k 位的进位,按照十进制加法规则,必然正确

📈 复杂度分析

复杂度类型 时间复杂度 空间复杂度
分析 O(max(m,n)) O(max(m,n))
说明 m, n 分别为两个链表的长度,需要遍历较长的链表 结果链表的长度最多为 max(m,n)+1
  • 时间复杂度:O(max(m,n)),其中 m 和 n 分别是两个链表的长度。我们需要遍历两个链表的每一位。
  • 空间复杂度:O(max(m,n)),用于存储结果链表。如果不考虑输出空间,额外空间复杂度为 O(1)。

📌 问题总结

关键要点

  • 理解链表逆序存储的含义(个位在头部)
  • 正确处理进位逻辑
  • 使用虚拟头节点简化边界情况处理
  • 考虑链表长度不一致的情况

常见陷阱

  • 忘记处理最后的进位(如 5+5=10 的情况)
  • 没有正确处理链表长度不同的情况
  • 在移动指针时出现空指针异常

扩展思考

  • 如果链表是正序存储(高位在前),该如何处理?(需要使用栈或递归)
  • 如何处理负数的情况?
  • 如果要实现链表的乘法,思路是什么?

github地址: https://github.com/swf2020/LeetCode-Hot100-Solutions

相关推荐
初夏睡觉2 小时前
每日一题( P1518 [USACO2.4] 两只塔姆沃斯牛 The Tamworth Two)(第二天)
算法
L_Aria2 小时前
3824. 【NOIP2014模拟9.9】渴
c++·算法·图论
gorgeous(๑>؂<๑)2 小时前
【ICLR26-Oral Paper】透过对比的视角:视觉语言模型中的自改进视觉推理
人工智能·算法·语言模型·自然语言处理
前路不黑暗@2 小时前
Java项目:Java脚手架项目通用基类和常量类的封装(九)
java·spring boot·笔记·学习·spring cloud·maven·intellij-idea
AC赳赳老秦2 小时前
软件组件自动化的革命:DeepSeek 引领高效开发新时代
运维·人工智能·算法·云原生·maven·devops·deepseek
小亮✿2 小时前
并查集OJ做题报告
算法·个人知识总结·做题报告
ShineWinsu2 小时前
对于模拟实现C++list类的详细解析—上
开发语言·数据结构·c++·算法·面试·stl·list
Mr YiRan2 小时前
C++语言类中各个重要函数原理
java·开发语言·c++
chilavert3182 小时前
技术演进中的开发沉思-370:final 关键字(上)
java·开发语言