【力扣-2. 两数相加 ✨】Python笔记

0 阅读6分钟

🔢 两数相加:链表界的“列竖式”大法

摘要:本文详解 LeetCode 2 题“两数相加”,通过“虚拟头节点 + 进位模拟”实现链表加法,深入剖析逆序存储的巧妙逻辑,助你掌握链表操作的核心技巧。


📚 核心知识点:链表模拟加法

这道题是链表操作的经典之作,核心在于模拟手工列竖式计算的过程。与数组不同,链表的逆序存储(低位在前,高位在后)完美契合了加法从低位向高位进位的计算顺序。

关键概念

  1. 虚拟头节点(Dummy Node) :创建一个临时节点作为结果链表的起点,避免了对头节点的特殊判断,让代码逻辑更统一、更优雅。
  2. 进位处理(Carry) :使用变量记录每一位相加后的进位值,传递给下一位计算。
  3. 时间与空间复杂度:时间复杂度为 O(max(m,n))(m、n 为链表长度),空间复杂度为 O(1)(除结果链表外无额外空间)。

📝 题目解析:LeetCode 2. 两数相加

题目描述
给你两个非空的链表,分别用来表示两个非负整数。其中,它们各自的位数是按照逆序的方式存储的,并且它们的每个节点只能存储一位数字。请你将这两个数相加,并以相同形式返回一个表示和的链表。

示例
输入:l1 = [2,4,3](代表数字 342),l2 = [5,6,4](代表数字 465)
输出:[7,0,8](代表数字 807,因为 342 + 465 = 807)


🚀 解题思路:像拉链一样计算

核心观察
因为链表是逆序存储的,所以链表的头节点就是数字的个位。这和我们手工做加法“从个位开始加”的顺序完全一致,无需反转链表!

解题步骤

  1. 初始化:创建虚拟头节点 dummy 和遍历指针 cur,初始化进位变量 carry = 0

  2. 遍历与计算:同时遍历两个链表:

    • 取出当前节点的值(如果链表已遍历完则取 0)。
    • 计算当前位的和:sum_val = x + y + carry
    • 计算当前位的结果:sum_val % 10
    • 更新进位:sum_val // 10
    • 创建新节点,接到结果链表,并移动指针。
  3. 处理最终进位:如果遍历结束后还有进位(carry > 0),需要额外创建一个节点存储该进位。

  4. 返回结果:返回 dummy.next


💻 代码实现(Python,带详细注释)

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next

class Solution:
    def addTwoNumbers(self, l1: Optional[ListNode], l2: Optional[ListNode]) -> Optional[ListNode]:
        # 1. 创建虚拟头节点,统一结果链表的创建逻辑
        dummy = ListNode(-1)
        cur = dummy  # 结果链表的遍历指针
        carry = 0   # 进位,初始为0
        
        # 2. 遍历两个链表,直到都遍历完
        while l1 or l2:
            # 取当前节点的值,链表遍历完则取0
            x = l1.val if l1 else 0
            y = l2.val if l2 else 0
            
            # 3. 计算当前位的和(包含进位)
            sum_val = x + y + carry
            # 当前位的结果:取模10
            current = sum_val % 10
            # 新的进位:整除10
            carry = sum_val // 10
            
            # 4. 创建新节点,接到结果链表
            cur.next = ListNode(current)
            cur = cur.next  # 结果指针后移
            
            # 5. 移动l1、l2指针(如果不为空)
            if l1:
                l1 = l1.next
            if l2:
                l2 = l2.next
        
        # 6. 处理最终进位:如果还有进位,额外创建节点
        if carry > 0:
            cur.next = ListNode(carry)
        
        # 7. 返回虚拟头的下一个节点(真正的结果链表头)
        return dummy.next

🧪 代码逐行演示(以示例 1 为例)

输入

  • l1 = 2 → 4 → 3(代表 342)
  • l2 = 5 → 6 → 4(代表 465)
  • 预期结果7 → 0 → 8(代表 807)
步骤l1值l2值进位carrysum_val当前位结果新进位链表状态
1250770dummy → 7
24601001dummy → 7 → 0
3341880dummy → 7 → 0 → 8
4NoneNone0---循环结束,无额外进位

最终结果7 → 0 → 8 ✅(对应 807,342+465=807)


⚠️ 关键细节与边界情况

  1. 链表长度不一致

    • 例如 l1 = 9→9→9→9→9→9→9l2 = 9→9→9→9
    • 处理逻辑:在遍历较短链表时,将其值视为 0,继续与较长链表的剩余部分相加。
  2. 最终进位处理

    • 例如 l1 = 9→9l2 = 9→9
    • 相加后得到 8→9,但此时进位 1 不能被忽略。循环结束后,必须额外创建一个节点 1,结果为 8→9→1
  3. 虚拟头节点的作用

    • 它像一个“占位符”,让我们不需要单独处理第一个节点的创建,所有节点的插入逻辑都是一致的,极大降低了代码出错的概率。

📊 复杂度分析

复杂度类型分析结果说明
时间复杂度O(max(m,n))只需要遍历较长链表的长度即可完成计算。
空间复杂度O(1)除了返回的结果链表外,只使用了常数个额外变量(指针、进位值等)。

🚀 进阶与同类题拓展

掌握了这个“模拟加法 + 进位 + 虚拟头”的模板,你可以轻松解决以下变种问题:

  • 🔹 445. 两数相加 II(正序存储):

    • 差异:数字最高位在链表头。此时无法直接从低位开始加。
    • 解法:需要先反转链表,或者使用来倒序处理数据。
  • 🔹 67. 二进制求和(字符串模拟):

    • 差异:输入是二进制字符串。
    • 解法:思路完全一致,只是进位规则变成了“逢二进一”。
  • 🔹 989. 数组形式的整数加法(数组模拟):

    • 差异:输入是数组。
    • 解法:同样适用模拟加法的思路,从数组末尾开始遍历。

📌 总结

  1. 核心思想:利用链表逆序存储的特性,模拟手工列竖式加法,配合虚拟头节点构建结果链表。
  2. 避坑指南:千万别忘了处理最后的进位,也别忘了处理长短不一的情况。
  3. 通用模板:这种“双指针遍历 + 进位变量 + 虚拟头节点”的模式,是解决所有“模拟加法”类问题的通用解法。

希望这篇文章能帮你彻底拿下这道经典面试题!如果有任何疑问,欢迎在评论区留言讨论。我是 爱摸鱼的打工仔,我们下期再见!🐟