2.
两数相加
原文题目
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
题目分析
题目中的意思就是,例如有两组数字,分别是 201
和 19
,那么他们相加的结果就是 220
,但是这两组数字是以链表的形式存储的,也就是说,我们需要将这两个链表进行相加,然后返回一个新的链表。
链表数据是以相反的顺序存储,链表的形式如下:
第一组链表:1
-> 0
-> 2
第二组链表:9
-> 1
相加的链表:0
-> 2
-> 2
js
const l1 = { val: 1, next: { val: 0, next: { val: 2, next: null } } }
const l2 = { val: 9, next: { val: 1, next: null } }
题解思路
方式一:转成数字相加
最简单的方式,把两个链表转成数字 201
和 19
进行相加,得出 220
再将其转为链表 0 -> 2 -> 2
js
const addTwoNumbers = function (l1, l2) {
const a1 = [] // [3, 4, 2]
const a2 = [] // [4, 6, 5]
while (l1 || l2) {
if (l1) {
a1.unshift(l1.val)
l1 = l1.next
}
if (l2) {
a2.unshift(l2.val)
l2 = l2.next
}
}
const n1 = a1.join('') // 342
const n2 = a2.join('') // 465
const n3 = String(Number(n1) + Number(n2)) // '807'
let head = { next: null }
let current = head
for (let index = n3.length - 1; index >= 0; index--) {
const node = { val: Number(n3[index]), next: null }
current.next = node
current = current.next
}
return head.next
}
let l1 = { val: 1, next: { val: 0, next: { val: 2, next: null } } }
let l2 = { val: 9, next: { val: 1, next: null } }
addTwoNumbers(l1, l2)
时间复杂度:O(2n)
空间复杂度:O(2n)
代码分析
- 首先通过
while
遍历两个链表,结合unshift
方法将其转为正序的数组。
js
while (l1 || l2) {
if (l1) {
a1.unshift(l1.val)
l1 = l1.next
}
if (l2) {
a2.unshift(l2.val)
l2 = l2.next
}
}
- 通过
join
将数组再次转成数字,并进行相加,得出807
js
const n1 = a1.join('') // 342
const n2 = a2.join('') // 465
const n3 = String(Number(n1) + Number(n2)) // '807'
- 最后将
807
转为反序链表即可,首先通过for
生成三组反序的node
节点
js
for (let index = n3.length - 1; index >= 0; index--) {
const node = { val: Number(n3[index]), next: null }
}
- 通过对象指针的思路,将三组
node
节点依次赋值给next
js
let head = { next: null }
let current = head
for (let index = n3.length - 1; index >= 0; index--) {
current.next = node
current = current.next
}
- 返回
head
的next
属性
js
return head.next
方式二:个位数相加,10 进位
按照正常的数学方式,就是直接 201
+ 19
= 220
,但是我们的数据是逆向存储的,所以转成数字相加在转回链表的复杂度会更高。所以可以通过从个位数相加,大于 10 就进位,这样一次遍历就可以完成。
个位数:1
+ 9
= 0
,因为进位 1
所以十位数额外 +1
十位数:0
+ 1
+ 1
= 2
,没有进位
百位数:2
+ 0
= 2
最终得出结果刚好是 022
,在一次遍历的过程中,可直接将 022
依次存储到链表中。
js
const addTwoNumbers = function (l1, l2) {
let head = { next: null }
let current = head
let carry = 0 // 是否进位
// 依次是个位数、十位数、百位数...、carry 位数
while (l1 || l2 || carry) {
let sum = carry
if (l1) {
sum += l1.val
l1 = l1.next
}
if (l2) {
sum += l2.val
l2 = l2.next
}
current.next = { val: sum % 10, next: null }
current = current.next
carry = Math.floor(sum / 10)
}
return head.next
}
let l1 = { val: 1, next: { val: 0, next: { val: 2, next: null } } }
let l2 = { val: 9, next: { val: 1, next: null } }
addTwoNumbers(l1, l2)
时间复杂度:O(n)
空间复杂度:O(n)
代码分析
- 首页还是通过
while
遍历两个链表的个位数、十位数、百位数...
js
while (l1 || l2) {}
while
循环第一次进来是个位数相加、第二次是十位数相加、第三次是百位数相加,以此类推。例如链表l1
的个位数是1
和链表l2
的个位数9
进行相加。
js
while (l1 || l2) {
let sum = 0
if (l1) {
sum += l1.val
l1 = l1.next
}
if (l2) {
sum += l2.val
l2 = l2.next
}
}
- 如果两个数相加大于等于 10,则需要进位,并且记录起来。例如个位数相加
1
+9
=10
,个位数的值是0
,百位数的值要加上进位的1
js
while (l1 || l2) {
let sum = carry // 例如百位数进来的时候,就自动在总和中 +1 了
current.next = { val: sum % 10, next: null } // 存储当前相加的结果
current = current.next
carry = Math.floor(sum / 10) // 计算当前总和 sum 是否达到进位标准
}
- 最后的位数相加如果大于
10
,需要多进入while
循环一次。例如我重新定义一个新的链表2
和链表8
,他们只有个位数,没有十位数。按照上方的思路while
循环只会进入一次,因为第二次进入的时候l1
和l2
都是null
,这就导致了个位数的结果是0
程序就结束了。正确的方式是十位数应该要是1
的。
js
while (l1 || l2 || carry) {}
所以 while
循环要加上 carry
判断条件,如果 carry
有值,按照新的举例来说,它的身份就是十位数。