LeetCode:链表(一)
本组囊括链表相关题目,难度不等。
2. Add Two Numbers
题目描述:中等
思路一:
主要难点是进位的问题,使用取模和整除来求解,具体看代码中的注释:
1 # Definition for singly-linked list. 2 # class ListNode: 3 # def __init__(self, x): 4 # self.val = x 5 # self.next = None 6 7 class Solution: 8 def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode: 9 10 # 链表, 用取模和整除来解 11 dummy = ListNode(0) # 预备哑节点/虚拟节点,但它不存储数字,初始化它和current相同。 12 current = dummy 13 carry = 0 # 初始化进位 14 while l1 or l2: 15 x = l1.val if l1 else 0 # 设置x为l1头节点的值,也就是个位数,如果l1节点已经到达了null,设置x为0 16 y = l2.val if l2 else 0 # 同上 17 sum = x + y + carry 18 carry = sum // 10 # 求进位是否为1 19 current.next = ListNode(sum%10) # 创建一个值为sum mod 10的节点,并将current的next指向它 20 if l1: # 防止下位为空,不为空则向前移动l1,l2的节点 21 l1 = l1.next 22 if l2: 23 l2 = l2.next 24 current = current.next # 同时 current的指向变为当前的新节点。 25 if carry != 0: # 判断carry是否等于1,如果等于1,在链表末尾增加一个为1的节点,这里只是个形式(防止后面没有数字了,直接进1,如果有数字会继续执行覆盖这个1),后面的sum会加一个carry为1. 26 current.next = ListNode(1) 27 return dummy.next # 返回dummy的next,也就是个位数开始的地方 28 # 初始化的节点dummy没有存储值,最后返回dummy的next 。这样的好处是不用单独对head进行判断改变值。也就是如果一开始的 head 就是代表个位数,那么开始初始化的时候并不知道它的值是多少,所以还需要在进入循环前单独对它进行值的更正,不能像现在一样只用一个循环简洁。 29 30 # 时间复杂度:O(max(m,n)),假设 m 和 n 分别表示 l1 和 l2 的长度,上面的算法最多重复max(m,n) 次。 31 # 空间复杂度:O(max(m,n)),新列表的长度最多为max(m,n)+1。
19. Remove Nth Node From End of List
题目描述:中等
思路一:
先计算链表长度的方法当然可行,但需要两次遍历链表;我们来考虑一下进阶所说的如何通过一遍扫描实现;
考虑使用两个指针,快慢指针都指向头节点;
(1)让快指针先前进n步,如果前进n步后head已经到达空节点,则证明原链表长度为n,删除倒数第n个节点即为删除第一个节点;
(2)如果前进n步快指针未到达空节点,则让慢指针与快指针同步前进,直到慢指针.next为空,这样的话慢指针就到达了要删除节点的前一节点处,再更改next指向即可。
1 class Solution: 2 def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode: 3 # 删除倒数第n个节点,即索引为len-n 4 # 考虑两个特殊位置,删除开头和结尾 5 # 考虑不用计算链表长度的方法 6 if not head: 7 return 0 8 dummy = ListNode(0) # 同样先设置一个哑节点,虚拟节点,val为None,指向第一个节点。方便最后返回本身的链表头结点 9 headc = head 10 dummy.next = head 11 for i in range(n): # 让head先前进n步 12 head = head.next 13 if not head:# 如果前进n步后head已经到达空节点,则证明原链表长度为n,删除倒数第n个节点即为删除第一个节点 14 headc = headc.next # 这样即是删除第一个节点 15 return headc 16 # 如果前进n步head未到达空节点,则让head与headc同步前进,直到head.next为空 17 while head.next != None: 18 head = head.next 19 headc = headc.next 20 # 设链表长度为n,则这样的话headc就走了len-1-n步到达要删除节点的前一节点 21 headc.next = headc.next.next # 此时使前一节点指向被删除节点所指向的下一节点 22 return dummy.next 23 # 时间复杂度:O(N),该算法对含有N个结点的列表进行了一次遍历。因此时间复杂度为O(N)。 24 # 空间复杂度:O(1),我们只用了常量级的额外空间。
21. Merge Two Sorted Lists
题目描述:简单
思路一:迭代解法
像极了合并两个有序数组。
遍历两个链表,每次返回值较低的节点;具体看代码注释:
1 # Definition for singly-linked list. 2 # class ListNode: 3 # def __init__(self, val=0, next=None): 4 # self.val = val 5 # self.next = next 6 class Solution: 7 def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode: 8 # 解法一:迭代解法 9 # 像极了合并两个有序数组。 10 # 遍历两个链表,每次返回值较低的节点 11 head = ListNode(0) # 固定格式,一个头节点用于生成新链表 12 dummy = head # 一个虚拟节点用于最后返回整个生成的链表 13 while l1 and l2: 14 if l1.val < l2.val: 15 head.next = l1 # 记住,next指向的是下一个节点而不是下一个节点的值!否则不会形成链表 16 head = head.next # 每次更新head指向 17 l1 = l1.next 18 else: 19 head.next = l2 20 head = head.next 21 l2 = l2.next 22 # 如果前面循环没有进行,则l1/l2其中一个为空链表,则直接返回另一个链表 23 # 如果前面循环进行了,在循环终止的时候, l1 和 l2 至多有一个是非空的。由于输入的两个链表都是有序的,所以不管哪个链表是非空的,它包含的所有元素都比前面已经合并链表中的所有元素都要大。这意味着我们只需要简单地将非空链表接在合并链表的后面,并返回合并链表即可。 24 if not l1: 25 head.next = l2 26 if not l2: 27 head.next = l1 28 return dummy.next 29 # 时间复杂度:O(N+M),M和N分别为两个链表的长度; 30 # 空间复杂度O(1)
思路二:递归
暂时留坑