给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。
如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。
您可以假设除了数字 0 之外,这两个数都不会以 0 开头。
题例:
输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807
第一种想法很明显的是将两个加数直接先算出来,然后相加再改成链表,速度上来讲需要遍历三次链表(两个加数以及结果),以及每次遍历时会进行乘以10再加一个新数的操作,链表越长,位数越高,计算越复杂,这是一个效率较低的方法。
def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode:
a = 0
b = 0
i = 0
j = 0
while l1 != None:
a = a + l1.val*pow(10, i)
i = i + 1
l1 = l1.next
while l2 != None:
b = b + l2.val*pow(10, j)
j = j + 1
l2 = l2.next
c = a + b
d = c % 10
l3 = ListNode(d)
c = c // 10
tail = l3
while c > 0:
d = c % 10
newNode = ListNode(d)
tail.next = newNode
tail = newNode
c = c // 10
return l3
运行效果很糟糕
考虑到加数与结果都是逆序排列的,可不可以直接每个节点之间相加,这样下来需要考虑的问题就是进位了,用一个变量记录上一位的进位,在计算当前位数的和时加上这个进位就可以了。
需要注意的就是边界情况,因为在算法竞赛中每一次的WA都会有扣分,需要将情况考虑完整,事实上,个人认为算法的两大难点一是算法本身,二就是边界情况了。
1.由于题设说明是非空链表,所以我在代码中才会直接取出两个加数链表的第一个数
2.退出循环时根据l1与l2的空与否有三种情况,需要进行分开讨论,在每一种情况下,不是说可以直接将循环中得出的最后一个节点接上非空的那一个链表:因为可能还有进位,需要对进位进行处理比如[1],[9,9,9],不是说先读入1+9=10,第一个节点是0,然后l1已经读完了就可以直接把99接到后面,那答案就是099,明显错误。
3.对于2中所说的三种情况,明显最多还剩一条链表没有进行处理,而对于l1和l2的处理方式是一样的,就可以将他们处理为同一变量名(字母l)实现简化代码的目的。而l1与l2都为空时呢?考虑到进位问题,l1与l2都为空时是不能直接返回的,只有在进位为0时才能返回,进位不为0呢?当然可以直接增加一个节点连在后面,但是又需要额外的代码量(后来想了一下只需要多一行,而且速度会提高),就将其处理为多加一个存储的数字为0的节点,统一到后面的代码中
4.现在考虑进位的问题:既然是进位导致的不能直接相连然后结束程序,那么这个循环的结束条件就应该是进位变量为0,这时候又出现一个问题,l由于要不停地next,是否next到为空时也应该停止处理呢,很明显考虑到进位不是这样的,l为空时当前位值是取不到的,需要进行单独处理。
def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode:
firstVal = l1.val + l2.val
ret = ListNode(firstVal % 10)
nowNode = ret
carry = firstVal // 10
l1 = l1.next
l2 = l2.next
while l1 != None and l2 != None:
nowVal = carry + l1.val + l2.val
newNode = ListNode(nowVal % 10)
nowNode.next = newNode
nowNode = newNode
carry = nowVal // 10
l1 = l1.next
l2 = l2.next
if l1 != None:
l = l1
elif l2 != None:
l = l2
else:
if carry == 0:
return ret
else:
l = ListNode(0)
while carry != 0:
if l != None:
nowVal = carry + l.val
l = l.next
else:
nowVal = carry
newNode = ListNode(nowVal % 10)
nowNode.next = newNode
nowNode = newNode
carry = nowVal // 10
if l != None:
nowNode.next = l
return ret
效率提升很明显
然后考虑到在进入循环前对第一个节点进行了单独处理,感觉代码不是很好看,看了参考答案发现是将第一个节点也放到了循环中,只是返回时返回的第一个节点的next
def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode:
ret = ListNode(0)
nowNode = ret
carry = 0
while l1 != None and l2 != None:
nowVal = carry + l1.val + l2.val
newNode = ListNode(nowVal % 10)
nowNode.next = newNode
nowNode = newNode
carry = nowVal // 10
l1 = l1.next
l2 = l2.next
if l1 != None:
l = l1
elif l2 != None:
l = l2
else:
if carry == 0:
return ret.next
else:
l = ListNode(0)
while carry != 0:
if l != None:
nowVal = carry + l.val
l = l.next
else:
nowVal = carry
newNode = ListNode(nowVal % 10)
nowNode.next = newNode
nowNode = newNode
carry = nowVal // 10
if l != None:
nowNode.next = l
return ret.next
很意外,效率提升了很多
然后仿照答案的规格,将循环退出条件由and变为or,效率下降是肯定的,但是代码会简洁很多
def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode:
ret = ListNode(0)
nowNode = ret
carry = 0
while l1 != None or l2 != None:
if l1 != None:
x = l1.val
else:
x = 0
if l2 != None:
y = l2.val
else:
y = 0
nowVal = x + y + carry
newNode = ListNode(nowVal % 10)
nowNode.next = newNode
nowNode = newNode
carry = nowVal // 10
if l1 != None:l1 = l1.next
if l2 !=None:l2 = l2.next
if carry > 0:
nowNode.next = ListNode(carry)
return ret.next
最后就是在写这篇博客时想到的,当l1和l2都为空时可以直接返回,不必为了代码简洁性单独处理
def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode:
ret = ListNode(0)
nowNode = ret
carry = 0
while l1 != None and l2 != None:
nowVal = carry + l1.val + l2.val
newNode = ListNode(nowVal % 10)
nowNode.next = newNode
nowNode = newNode
carry = nowVal // 10
l1 = l1.next
l2 = l2.next
if l1 != None:
l = l1
elif l2 != None:
l = l2
else:
if carry != 0:
l = ListNode(1)
nowNode.next = l
return ret.next
while carry != 0:
if l != None:
nowVal = carry + l.val
l = l.next
else:
nowVal = carry
newNode = ListNode(nowVal % 10)
nowNode.next = newNode
nowNode = newNode
carry = nowVal // 10
if l != None:
nowNode.next = l
return ret.next
这下就差不多了