(LeetCode-Hot100)2. 两数相加

2. 两数相加

LeetCode 题目链接

❌|✅|💡|📌 问题简介

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

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

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

题目描述

  • 输入:两个单链表 l1 和 l2
  • 输出:一个新的单链表,表示 l1 和 l2 所代表数字之和
  • 约束:
    • 每个链表节点值为 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. 循环处理:

    • 当 l1 或 l2 还有节点,或者还有进位时,继续循环
    • 获取当前位的值:如果链表还有节点就取值,否则为 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

相关推荐
GG.WinGel9 分钟前
质数数目(蓝桥杯练习题)
java·数据结构·算法·青少年编程·蓝桥杯
泡泡糖的中文规格书14 分钟前
HDLbits 2.2 学习笔记 · 多路复用器
java·开发语言
艾iYYY15 分钟前
进程的状态
算法
John_Hui17 分钟前
第 3 篇:配置 JDK 17 环境
java·开发语言·ubuntu
秋天的怀念_David22 分钟前
算法一千零一夜 · 第十七夜 长街点灯
java·程序人生·算法
CV爱数码26 分钟前
论文精读|RED-CNN:残差编解码网络,低剂量 CT 去噪的经典基线
网络·人工智能·算法·cnn·ct·重建·x-ray
不吃鱼的羊37 分钟前
如何应对恐惧
java
不吃鱼的羊39 分钟前
如何建立高自尊
java·服务器·前端
Magicdata大数据40 分钟前
AI辅助教学:调用大模型从教材中生成习题,方便学员练习
java·spring boot·大模型·知识图谱·neo4j·ai辅助教学
香蕉大香蕉(06敬畏市场)1 小时前
高频八股 · Java 并发编程(10):异步、ThreadLocal 与虚拟线程(收官)
java·开发语言·网络·jvm·python·ai·实习