参考:https://labuladong.github.io/algo/1/8/
21. 合并两个有序链表
简单的遍历链表即可,没什么技巧。数据结构课程经典例题。
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 30 31
| from typing import Optional
class ListNode: def __init__(self, val=0, next=None): self.val = val self.next = next
class Solution: def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]: dummy = ListNode() l1 = list1 l2 = list2 res = dummy while l1 and l2: if l1.val < l2.val: dummy.next = l1 l1 = l1.next else: dummy.next = l2 l2 = l2.next dummy = dummy.next if l1: dummy.next = l1 if l2: dummy.next = l2 return res.next
|
23. 合并K个升序链表
经典的优先队列问题。
利用优先队列来处理,但是这里的实现不怎么好,因为这里是一次性把所有的节点全部都加入到优先队列中去,然后再一个一个弹出来。其实我的想法是单独给 ListNode 类配置一个比较函数,这样,我们可以每次把每一条链表的头节点加进去即可。
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 30 31 32 33 34 35 36 37 38 39
| from typing import List, Optional from queue import PriorityQueue
class ListNode: def __init__(self, val=0, next=None): self.val = val self.next = next
class Solution: def mergeKLists(self, lists: List[Optional[ListNode]]) -> Optional[ListNode]: pq = PriorityQueue() dummy = ListNode(-1) res = dummy for i in range(len(lists)): curNode = lists[i] while curNode: pq.put(curNode.val) curNode = curNode.next while not pq.empty(): dummy.next = ListNode(pq.get()) dummy = dummy.next
return res.next
if __name__ == '__main__': solu = Solution() lists = [ListNode(1), ListNode(2), ListNode(3)] res = solu.mergeKLists(lists) while res: print(res.val) res = res.next
|
19. 删除链表的倒数第 N 个结点
一趟遍历,利用一个小技巧,先走 n 步数,然后再来一个指针,同时走,第一个指针走到最后一个节点的时候,刚好第二个指针走到了倒数第 n 个节点的前一个节点。
注意一下删除倒数 n 个节点的情况,即删除第一个节点的情况。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| class ListNode: def __init__(self, val=0, next=None): self.val = val self.next = next
class Solution: def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode: res = head headCopy = head for _ in range(n): headCopy = headCopy.next if not headCopy: return res.next while headCopy.next: head = head.next headCopy = headCopy.next head.next = head.next.next return res
|
876. 链表的中间结点
简单的快慢指针技巧。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class ListNode: def __init__(self, val=0, next=None): self.val = val self.next = next
class Solution: def middleNode(self, head: ListNode) -> ListNode: slowP = head fastP = head while fastP and fastP.next: fastP = fastP.next.next slowP = slowP.next
return slowP
|
141. 环形链表
依然是快满指针技巧。
这里还要证明一个点,就是一定能相遇吗?
答:能。假设慢指针走了 $k$ 步,则快指针走了 $2k$ 步,用 $m$ 表示环的节点数。我们只要找到一个 $a$,使得下面式子成立即可:
$$
2k - k = am
$$
因为 $k$ 为任意正整数,所以 $a = \displaystyle\frac{k}{m}$ 一定可以取到。证毕。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| from typing import Optional
class ListNode: def __init__(self, x): self.val = x self.next = None
class Solution: def hasCycle(self, head: Optional[ListNode]) -> bool: if not head or not head.next: return False slowP = head fastP = head while fastP and fastP.next: slowP = slowP.next fastP = fastP.next.next if slowP == fastP: return True return False
|
142. 环形链表 II
这道题第一次是觉得很难的。当时好像看了题解也想得不是很明白。当时用的还是 C 语言。
现在想来,不过是一道小学的追及问题。
利用快慢指针。有一个困惑我的点,就是真的不会出现快指针先跨越了慢指针,然后在之后的路程中再和慢指针相遇吗?答案当然是不可能的。我们可以把每一次要走的距离用单位 1 来考虑,如果快指针想要跨越慢指针,那么一定是建立在快慢指针已经相遇的情况下。
然后问题就好解决了。我们把慢指针进入环之前走过的距离设为 $x$,把进入环之后直到相遇点之间的距离设为 $y$,然后,我们把慢指针重设为 head
,然后让快慢指针同时以每次步进 1 的速率往后面走,那么,它们在环的起点一定会相遇。
因为第一次相遇的时候,慢指针走了 $x + y$ 步,快指针走了 $2(x + y)$ 步,然后快指针是比慢指针多走了一圈环的,那么,快指针如果想要再次走到环的起点,需要走 $x$ 步,而我们慢指针重设后,要走到环的起点,也是要走 $x$ 步。思路就显而易见了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| class ListNode: def __init__(self, x): self.val = x self.next = None
class Solution: def detectCycle(self, head: ListNode) -> ListNode: slowP = head fastP = head while fastP and fastP.next: slowP = slowP.next fastP = fastP.next.next if slowP == fastP: slowP = head while not fastP == slowP: slowP = slowP.next fastP = fastP.next return fastP
return None
|
160. 相交链表
这题的思路是拼接。
假设 A 由 a 和 c 组成,B 由 b 和 c 组成,那么,把 B 拼到 A 的后面,就变成了 a + c + b + c,把 A 拼到 B 的后面,就是 b + c + a + c,前面的 a + c + b
和 b + c + a
长度是相同的,那么找到相交节点就比较容易了。
还有一种思路,就是利用环形链表找入口的方法。也比较容易理解。也容易想。
写法一:
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 30 31 32 33 34 35 36
| class ListNode: def __init__(self, x): self.val = x self.next = None
class Solution: def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode: p1 = headA p2 = headB while p1 != p2: if not p1: p1 = headB else: p1 = p1.next if not p2: p2 = headA else: p2 = p2.next
return p1
if __name__ == '__main__': solu = Solution() headA = ListNode(1) headA.next = ListNode(9) headA.next.next = ListNode(1) headB = ListNode(3) headC = ListNode(2) headC.next = ListNode(4) headA.next.next.next = headC headB.next = headC print(solu.getIntersectionNode(headA, headB).val)
|
写法二:
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 30 31 32 33 34 35 36 37 38 39 40
| class ListNode: def __init__(self, x): self.val = x self.next = None
class Solution: def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode: p1 = headA p2 = headB flag = 1 while p1 and p2: if p1 == p2: return p1 if not p1.next and flag: p1 = headB flag = 0 else: p1 = p1.next if not p2.next: p2 = headA else: p2 = p2.next
return p1
if __name__ == '__main__': solu = Solution() headA = ListNode(1) headA.next = ListNode(9) headA.next.next = ListNode(1) headB = ListNode(3) headC = ListNode(2) headC.next = ListNode(4) headA.next.next.next = headC headB.next = headC print(solu.getIntersectionNode(headA, headB).val)
|
这个写法二让我调试了好长时间。虽然思路简单,但是实际跑的时候还得注意细节。