LeetCode 445 - 两数相加 II


文章目录

摘要

这道题是经典链表加法的"升级版"。和普通相加不同的是:

  • 数字是"从高位到低位"存储的(反着来)。
  • 输入链表不能被翻转(进阶要求)。

因此不能直接像 LeetCode 2 那样从头开始相加,只能想办法把最高位的数字放到最后进行计算。

最常见的做法是:

  • 把两个链表的值分别压入两个栈
  • 从栈顶开始做加法(栈顶就是最低位)
  • 边算边构建新链表(头插法)

这种写法特别清晰,也符合进阶要求。

描述

我们要处理两个非空链表,每个节点只存储一位数字,而且链表从头到尾是从"最高位 → 最低位"。

举个例子:

txt 复制代码
7 → 2 → 4 → 3

代表的数字是 7243

另一个:

txt 复制代码
5 → 6 → 4

代表的是 564

两者相加:

txt 复制代码
7243 + 564 = 7807

得到:

txt 复制代码
7 → 8 → 0 → 7

链表顺序不能反转,也不能存成数组再反转,所以需要用栈来处理。

题解答案

核心思路:

  1. 遍历两个链表,把值分别压入两个栈。
  2. 栈顶的元素就是最低位,所以我们从栈顶开始相加。
  3. 进位正常处理。
  4. 每次计算结果后创建新节点,并将该节点插入链表头部。
  5. 遍历结束后返回头节点。

这类问题在工程里其实很常见,比如:

  • 处理日志编号:左边是高位,右边是低位,需要从右边开始加
  • 做大整数运算:避免翻转原始结构
  • 流式处理数字时需要用先入后出的方式处理高位数据

链表 + 栈的组合非常优雅。

题解代码分析(附可运行 Swift Demo)

下面这段 Swift 代码可以直接运行。

swift 复制代码
import Foundation

public class ListNode {
    public var val: Int
    public var next: ListNode?
    public init(_ val: Int) {
        self.val = val
        self.next = nil
    }
}

class Solution {
    func addTwoNumbers(_ l1: ListNode?, _ l2: ListNode?) -> ListNode? {
        var stack1 = [Int]()
        var stack2 = [Int]()
        
        // 把链表值压入栈
        var p = l1
        while p != nil {
            stack1.append(p!.val)
            p = p!.next
        }
        
        var q = l2
        while q != nil {
            stack2.append(q!.val)
            q = q!.next
        }
        
        var carry = 0
        var head: ListNode? = nil
        
        // 两个栈都空且没有进位时才能结束
        while !stack1.isEmpty || !stack2.isEmpty || carry > 0 {
            let x = stack1.popLast() ?? 0
            let y = stack2.popLast() ?? 0
            
            let sum = x + y + carry
            carry = sum / 10
            
            let node = ListNode(sum % 10)
            // 头插法,把当前结果挂到前面
            node.next = head
            head = node
        }
        
        return head
    }
}


// MARK: - Demo 测试
func buildList(_ arr: [Int]) -> ListNode? {
    let dummy = ListNode(0)
    var cur = dummy
    for num in arr {
        cur.next = ListNode(num)
        cur = cur.next!
    }
    return dummy.next
}

func printList(_ head: ListNode?) {
    var p = head
    var res = [Int]()
    while p != nil {
        res.append(p!.val)
        p = p!.next
    }
    print(res)
}

func runDemo() {
    let sol = Solution()
    
    let l1 = buildList([7,2,4,3])
    let l2 = buildList([5,6,4])
    let ans1 = sol.addTwoNumbers(l1, l2)
    print("示例 1 输出:", terminator: "")
    printList(ans1)
    
    let l3 = buildList([2,4,3])
    let l4 = buildList([5,6,4])
    print("示例 2 输出:", terminator: "")
    printList(sol.addTwoNumbers(l3, l4))
    
    let l5 = buildList([0])
    let l6 = buildList([0])
    print("示例 3 输出:", terminator: "")
    printList(sol.addTwoNumbers(l5, l6))
}

runDemo()

示例测试及结果

下面是 Demo 输出的结果:

示例 1

txt 复制代码
输入: [7,2,4,3] + [5,6,4]
输出: [7,8,0,7]

符合预期的 7807

示例 2

txt 复制代码
输入: [2,4,3] + [5,6,4]
输出: [8,0,7]

因为 243 + 564 = 807

示例 3

txt 复制代码
输入: [0] + [0]
输出: [0]

最简单的情况。

与实际场景关联

这个题型在真实业务中非常有用,尤其是处理数字从高位到低位排列的数据结构时,例如:

1. 构建银行流水号或订单 ID

有些系统会把订单号拆成链式结构,按高位存储,这时候加法操作不能破坏原始顺序。

2. 事件流中的时间戳分段存储

日志系统可能会按层级分段存储时间戳,高位年份在前,低位毫秒在后,需要从后往前加。

3. 大数运算引擎实现

大数库需要支持不可逆结构,不能频繁翻转内部结构,这种题的解法就非常适用。

时间复杂度

遍历两个链表压栈:
O(n + m)

相加过程又是一次遍历两个栈:
O(n + m)

整体:

O(n + m)

空间复杂度

使用两个栈保存链表值:
O(n + m)

返回链表占用的空间不算额外空间(题意如此)。

总结

这道题用链表直接相加会很麻烦,用"两个栈"来倒置链表顺序,是最自然且满足进阶要求的解法。

解决关键有三个:

  1. 栈让我们能从最低位开始加。
  2. 头插法让结果保持从高位到低位。
  3. 进位逻辑保持标准大数加法的写法。
相关推荐
monster000w3 小时前
大模型微调过程
人工智能·深度学习·算法·计算机视觉·信息与通信
小小晓.3 小时前
Pinely Round 4 (Div. 1 + Div. 2)
c++·算法
SHOJYS3 小时前
学习离线处理 [CSP-J 2022 山东] 部署
数据结构·c++·学习·算法
biter down3 小时前
c++:两种建堆方式的时间复杂度深度解析
算法
zhishidi3 小时前
推荐算法优缺点及通俗解读
算法·机器学习·推荐算法
WineMonk3 小时前
WPF 力导引算法实现图布局
算法·wpf
2401_837088503 小时前
双端队列(Deque)
算法
ada7_4 小时前
LeetCode(python)108.将有序数组转换为二叉搜索树
数据结构·python·算法·leetcode
奥特曼_ it4 小时前
【机器学习】python旅游数据分析可视化协同过滤算法推荐系统(完整系统源码+数据库+开发笔记+详细部署教程)✅
python·算法·机器学习·数据分析·django·毕业设计·旅游