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. 进位逻辑保持标准大数加法的写法。
相关推荐
倒头就睡的小比特1 天前
算法竞赛C++常用的STL
c++·算法
小羊没烦恼!1 天前
初探性能优化——2个月到4小时的性能提升
java·开发语言·windows·算法·c#
猎头南楼1 天前
知识社区推荐系统实践:新用户冷启动与长短期兴趣建模的挑战 资深推荐算法工程师
人工智能·深度学习·算法·机器学习
旖旎夜光1 天前
力控面试题 01.01: 判定字符是否唯一(位运算) —— 题解
c++·学习·算法·leetcode·力控
wzdark1 天前
大规模并行计算中的负载均衡算法研究4
算法
Because_of_Her11 天前
并查集-听课笔记
笔记·算法·并查集
码流子1 天前
高速公路安全监测实践:碰撞监测预警+物联网底座,从感知到处置的闭环
大数据·人工智能·物联网·算法·架构
another heaven1 天前
【算法/C++ MD5算法能否逆解码?原理、C++实现与同类哈希算法对比】
c++·算法·哈希算法
wzdark1 天前
从算法设计模式看编程思维的抽象能力4
算法
2601_962218611 天前
万象生鲜系统称重自动多退少补算法解决生鲜非标品痛点
大数据·数据库·人工智能·python·算法