LeetCode第2题:两数相加
题目描述:
要求:
第一种解法:遍历逐位相加
这题相比第一题,采用了单链表这个数据结构来进行。在解该题时,可以先把我们的思维带入,两数相加在我们实际生活中是如何计算的,两数从低位开始对齐,然后逐渐相加,而题目中正好是逆序存放,这就刚刚好符合我们计算时的模式,因此我们只需要新建一个单链表,然后依次遍历所给的链表L1和L2的所有结点,并将其对应的val值相加赋给我们新建的单链表结点即可
result = ListNode() #初始链表
我新建了一个result链表,根据下图的定义便可以知道这个新建的result的val和next会分别赋予0和None
在我们实际计算的时候,还会碰到进位的情况,因此还需要设置一个进位变量以便后续的计算进行
carry = 0 #进位标志
设置了初始链表和进位标志后,因为后续是需要移动链表的,而单链表的性质迫使指针next无法指向上一个结点,因此每当移动一次后,前面的数据就无法查找,因此我们需要再定义一个链表,令result指向它,这时result就起到了头指针的作用,让我们在后面可以返回正确的链表
tmp = result
做完了初始工作,就开始解题了,首先我们要判断所给的L1和L2哪个链表长度短,因为next指针指向的是None,不一致的话超过部分无法直接相加,因此先判断哪个链表短,这时只需要用一个while循环条件判断即可,代码如下:
while l1 and l2:
因为while的条件判断是True时执行,and运算会使当其中一个链表为空时变为False,这时就会停止while循环判断
接下来就是对链表进行赋值了,首先先计算L1和L2当前的val并和carry(进位)值相加,这样就得到了最低位的相加结果
sum = l1.val + l2.val + carry
接下来就对tmp进行赋值,我们让tmp的下一个指针指向的val值等于sum模10,这样就取得了第一个数,模10的原因是因为当相加超过10时可以去到个位数的值,如相加得12,那么个位数就是2,符合我们正常的计算,此时,进位就是1,那么进位的表达式就是sum整除10
tmp.next = ListNode(sum % 10)
carry = sum // 10
然后就是移动指针方向,使其指向下一个结点
l1 = l1.next
l2 = l2.next
tmp = tmp.next
这样while循环就结束了,但是while循环的作用是讲两个链表从第一个开始相加,加至等长部分,那么当链表不等长时,就需要额外再进行相加,所以用if判断语句来进行,赋值方式同while循环那块代码类似
if l1:
while l1 :
sum = l1.val + carry
tmp.next = ListNode(sum % 10)
carry = sum // 10
l1 = l1.next
tmp = tmp.next
if l2:
while l2 :
sum = l2.val + carry
tmp.next = ListNode(sum % 10)
carry = sum // 10
l2 = l2.next
tmp = tmp.next
那么为什么要进行这么长的赋值而不直接讲tmp的指针直接指向L1(或L2)的剩余部分链下去呢?这是因为存在进位情况,万一出现进位,那么L1(或L2)的剩余部分的val值将会产生改变,直接链下去就会使结果产生错误
再接下来,因为我们之前计算的结束条件是L1和L2都到空的情况,但是如果最后一位还产生进位情况时,就无法得到正确结果,因此还需要对进位进行判断,若carry为1时则再新建一个结点并赋值为1
if carry == 1:
tmp.next = ListNode(1)
最后,我们返回result的下一个结点即可,因为第一个结点是0,充当的是头结点的作用,无需返回第一个结点
return result.next
完整代码如下:
class Solution:
def addTwoNumbers(self, l1: Optional[ListNode], l2: Optional[ListNode]) -> Optional[ListNode]:
carry =0
result = ListNode()
tmp = result
while l1 and l2 :
sum = l1.val + l2.val + carry
tmp.next = ListNode(sum % 10)
carry = sum // 10
l1 = l1.next
l2 = l2.next
tmp = tmp.next
if l1:
while l1 :
sum = l1.val + carry
tmp.next = ListNode(sum % 10)
carry = sum // 10
l1 = l1.next
tmp = tmp.next
if l2:
while l2 :
sum = l2.val + carry
tmp.next = ListNode(sum % 10)
carry = sum // 10
l2 = l2.next
tmp = tmp.next
if carry == 1:
tmp.next = ListNode(1)
return result.next
复杂度分析
时间复杂度:因为我们先是遍历的是所给的链表L1和L2的等长部分,再遍历剩余部分,因此实际上就是遍历了L1和L2当中长度最长的那个,设L1长度为m,L2长度为n,则时间复杂度为O(max(m,n)),即取长度最长的链表
空间复杂度:空间复杂度取决于我们新建的链表result的长度,最长就是max(m,n)+1,即最后一位还有进位的情况,那这个+1其实无关痛痒,可以省略,因此空间复杂度为O(max(m,n))
优缺
暂时还不知道优缺点,有知道的可以评论告诉我下(doge)
第二种解法:递归
该解法参考LeetCode第二题讨论区的名叫:FractalPhi的用户
首先是判断L1和L2是否非空,若L1空,则返回L2,若L2空,则返回L1,这里因为第一种只需要调用一次addTwoNumbers函数即可得到结果,而且题目给的是非空链表,因此第一种解法不需要判断空还是不空,但是递归解法是链表的递归后一直调用addTwoNumers函数,会有链表为空的情况产生,因此需要判断
if not l1:
return l2
if not l2:
return l1
接下去就进行对应位加法操作
sum = l1.val + l2.val
紧接着就开始进行递归,返回值是结点
if sum > 9:
return ListNode(sum-10, self.addTwoNumbers(ListNode(1, None), self.addTwoNumbers(l1.next, l2.next)))
else:
return ListNode(sum, self.addTwoNumbers(l1.next, l2.next))
我们以输入L1=[2,4,3],L2=[5,6,4]为例来进行讲解
1、首先先判断最低位相加,2+5=7,7<9,进行else部分,此时返回的是一个结点,值为7,next指针指向的是调用addTwoNumbers函数后的某个未知结点①,这个结点①是经过又调用addTwoNumbers函数得来的,我们来看接下去的运行如何确定结点①
2、接下去进行上一步中的递归self.addTwoNumbers(l1.next,l2,next),此时传入的是L1和L2的next,此时运行函数addTwoNumbers,现在sum的值为4+6=10,10>9,进行的是if sum > 9:的部分,此时返回的结点值是sum-10=0,指针指向的是某个未知结点②,这个结点②是也是调用函数相加得来,但是在传入的两个链表参数中,第二个链表需要递归后才能得到,按照执行顺序,我们需再执行addTwoNumbers函数才能确定结点②
3、这时候进行的是确定结点②,依照上一步,我们先计算addTwoNumbers函数返回的结点,此时sum=3+4=7,7<9,执行的是else部分,此时返回了一个未知结点③,③是通过函数计算得来,此时再进行函数计算,因为现在L1和L2的元素都已经为空,根据判断条件,所以返回的是L2,L2是一个空的链表,因此未知结点③的val值为7,next指针指向None
4、确定了未知结点③,此时就开始确定未知结点②,②是由ListNode(1,None)和结点③(7,None)调用函数相机得到的,所以结点②的val值为8,指针指向空
5、结点②确定了,就能够确定结点①,结点①是由ListNode(0,结点②)得来的,因此结点①的val值为1,next指针指向的是结点②
6、到此就结束了递归之旅,因此可以确定,此时的链表关系是(7,结点①),结点①(0,结点②),结点②(8,空),正好是正确答案
过程如图(省略计算)
复杂度分析
时间复杂度:递归的深度就是两个链表最长的长度,即O(max(m,n)
空间复杂度:还是建立的链表长度,即O(max(m,n)
优缺
还是不懂,唯一知道的是递归的缺点就是可能会出现内存溢出,因为在递归调用的过程中都会在内存栈中分配空间,这当长度很长时可能会出现溢出,while循环则只是调用一次addTwoNumbers函数,每次循环后都会结束并且释放内存,并不会一直在内存栈中请求空间,所以无此忧虑
以上请大家多批评指正!
图均参考LeetCode