算法:两数相加

题目

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

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

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

示例 1:

img

1
2
3
输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807.

示例 2:

1
2
输入:l1 = [0], l2 = [0]
输出:[0]

示例 3:

1
2
输入:l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9]
输出:[8,9,9,9,0,0,0,1]

提示:

  • 每个链表中的节点数在范围 [1, 100]
  • 0 <= Node.val <= 9
  • 题目数据保证列表表示的数字不含前导零

解法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
func addTwoNumbers(_ l1: ListNode?, _ l2: ListNode?) -> ListNode? {
if l1 == nil || l2 == nil {
return l1 ?? l2
}

var cur1 = l1
var cur2 = l2
let sum = cur1!.val + cur2!.val
var carry = sum / 10
let head = ListNode(sum % 10)
var cur: ListNode? = head

while cur1?.next != nil || cur2?.next != nil {
let value1 = cur1?.next?.val ?? 0
let value2 = cur2?.next?.val ?? 0
let sum = value1 + value2 + carry
carry = sum / 10
cur?.next = ListNode(sum % 10)
cur = cur?.next
cur1 = cur1?.next
cur2 = cur2?.next
}

if carry > 0 {
cur?.next = ListNode(carry)
}

return head
}

解题思路

两数相加问题的主要思路是,将两个数字表示成逆序链表,每个节点存储一个数字位。步骤如下:

  1. 从头节点开始相加:由于链表是逆序存储,链表的头节点就是数字的个位,从最低位开始相加。

  2. 计算和与进位:将对应的两个节点的值相加,同时加上上一次运算的进位,得到当前的和。

  • 令当前位的值为 sum 模 10,即 digit = sum % 10

  • 更新进位为 sum 除以 10,即 carry = sum / 10

  1. 构造结果链表:每计算出一位的值,就构造一个新的节点,将其接入结果链表中。

  2. 遍历所有节点:同时遍历两个链表,直到两个链表的所有节点都被处理完毕。如果还有进位,则需要在链表末尾插入一个新节点来表示该进位。

  3. 考虑特殊情况:如果输入链表为空或只存在其中一个链表的情况,也要正确处理。

代码实现详细讲解

下面详细解释代码中每一部分的功能和实现细节:

1
2
3
if l1 == nil || l2 == nil {
return l1 ?? l2
}

输入判断:

如果任意一个链表为空,直接返回非空的那个链表。

其中,⁠l1 ?? l2 表示如果 ⁠l1 非空则返回 ⁠l1,否则返回 ⁠l2


1
2
3
4
5
6
var cur1 = l1
var cur2 = l2
let sum = cur1!.val + cur2!.val
var carry = sum / 10
let head = ListNode(sum % 10)
var cur: ListNode? = head

变量初始化:

  • cur1 和 ⁠cur2 分别作为遍历 ⁠l1 和 ⁠l2 的指针。

  • 首先对两个链表的头节点(个位)进行相加,计算总和 ⁠sum = cur1!.val + cur2!.val

  • 根据 ⁠sum 计算进位 ⁠carry = sum / 10

  • 新建结果链表的头节点,节点的值为 ⁠sum % 10(当前位的结果)。

  • 定义一个 ⁠cur 指针指向当前处理的结果链表尾部,方便后续的节点追加。


1
2
3
4
5
6
7
8
9
10
while cur1?.next != nil || cur2?.next != nil {
let value1 = cur1?.next?.val ?? 0
let value2 = cur2?.next?.val ?? 0
let sum = value1 + value2 + carry
carry = sum / 10
cur?.next = ListNode(sum % 10)
cur = cur?.next
cur1 = cur1?.next
cur2 = cur2?.next
}

遍历剩余节点:

使用 ⁠while 循环处理两个链表中剩余的节点,条件是任意一个链表还有后续节点。

  • 通过 ⁠cur1?.next?.val ?? 0 获取 ⁠cur1 的下一个节点的值,如果不存在则赋值为 0。

  • 同理,⁠value2 = cur2?.next?.val ?? 0 获取 ⁠cur2 的下一个节点的值。

  • 将当前位上的两个数字及前一次的进位相加,得到新的 ⁠sum

  • 更新进位 ⁠carry = sum / 10

  • 新建一个节点保存当前位的值 ⁠sum % 10 并接到结果链表后面。

  • 移动指针 ⁠cur1 和 ⁠cur2 指向下一个节点,继续下一轮相加;同时更新 ⁠cur 指针指向新加入的节点。


1
2
3
if carry > 0 {
cur?.next = ListNode(carry)
}

处理最后的进位:

循环结束后,可能最后一次运算后仍有进位(例如相加得到的和大于等于 10)。

如果 ⁠carry 大于 0,则新建一个节点保存进位,并将其接入链表末尾。