文章目录
1. 一文搞懂单链表的六大解题套路
21.合并两个有序链表(简单)
简单题,双指针就可以,还有一个传值还是传引用的问题可以看这篇:python函数传参是传值还是传引用?
# Definition for singly-linked list.
# 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()
p = dummy
while list1 != None and list2 != None:
if list1.val < list2.val:
p.next = list1
list1 = list1.next
p = p.next
elif list2.val <= list1.val:
p.next = list2
list2 = list2.next
p = p.next
if list1 != None:
p.next = list1
if list2 != None:
p.next = list2
return dummy.next
23.合并K个升序链表(困难)
与上面那道题一样的思路,只不过上面那道题只需要比较两个指针的值,这道题有k个,要是挨个比较要o(k)的复杂度。但是我们可以用priority queue这个数据结构把时间复杂度降到o(logk)
python 里面priority queue怎么用,看这篇文章: 用Python实现优先级队列
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def mergeKLists(self, lists: List[ListNode]) -> ListNode:
def __lt__(self, other):
return self.val < other.val
ListNode.__lt__ = __lt__
dummy = ListNode()
p = dummy
heap = []
for ln in lists:
if ln:
heapq.heappush(heap, ln)
while heap:
smallest = heapq.heappop(heap)
p.next = smallest
p = p.next
if smallest.next:
heapq.heappush(heap, smallest.next)
return dummy.next
注意的点有:
- 要修改ListNode.lt 方法以便使用heapq
- 原题中某些ListNode可能是NoneType,所以heappush的时候要加以判断, 保证加入的ListNode不是None
- 在while循环中, smallest的下一个元素在被push到heap里之前也要判断, 不能加入None
141.环形链表(简单)
142.环形链表 II(中等)
19.删除链表的倒数第 N 个结点(中等)
876.链表的中间结点(简单)
以上这些题都可以用快慢指针来做
160.相交链表(简单)
如果用两个指针 p1 和 p2 分别在两条链表上前进,并不能同时走到公共节点,也就无法得到相交节点 c1。
解决这个问题的关键是,通过某些方式,让 p1 和 p2 能够同时到达相交节点 c1。
所以,我们可以让 p1 遍历完链表 A 之后开始遍历链表 B,让 p2 遍历完链表 B 之后开始遍历链表 A,这样相当于「逻辑上」两条链表接在了一起。
如果这样进行拼接,就可以让 p1 和 p2 同时进入公共部分,也就是同时到达相交节点 c1
需要注意的是,不相交的情况也可以看作相交于None这个节点,也就是下图c1是None的情况
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
pa = headA
pb = headB
while pa != pb:
if pa:
pa = pa.next
else:
pa = headB
if pb:
pb = pb.next
else:
pb = headA
return pa
2.递归反转链表的一部分递归反转链表的一部分
206. 反转链表(简单)
- 递归解法
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
if head is None or head.next is None:
return head
last = self.reverseList(head.next)
head.next.next = head
head.next = None
return last
这里注意basecase 当链表为空或者只有一个节点, 反转就是自身
- 迭代
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
prev = None
cur = head
while cur:
next = cur.next
cur.next = prev
prev = cur
cur = next
return prev
prev->cur->next
注意为什么要先取得next, 其实next相当于一个temp变量, 暂时把cur的下一个节点存起来,因为接下来cur要与prev相连用于颠倒指针指向, 如果没有存next, cur将无法移动到下一个节点.
cur与prev相连之后可以把cur和prev同时向后移动一位
92.反转链表II(中等)
- 递归
第一步 反转链表前 N 个节点
def reverseN(head, right):
if right == 1:
successor = head.next
return head, successor
last, successor = reverseN(head.next, right-1)
head.next.next = head
head.next = successor
return last, successor
1、base case 变为 n == 1,反转一个元素,就是它本身,同时要记录后驱节点。
2、刚才我们直接把 head.next 设置为 null,因为整个链表反转后原来的 head 变成了整个链表的最后一个节点。但现在 head 节点在递归反转之后不一定是最后一个节点了,所以要记录后驱 successor(第 n + 1 个节点),反转之后将 head 连接上。
第二步:
class Solution:
def reverseBetween(self, head: ListNode, left: int, right: int) -> ListNode:
def reverseN(head, right):
if right == 1:
successor = head.next
return head, successor
last, successor = reverseN(head.next, right-1)
head.next.next = head
head.next = successor
return last, successor
if left == 1:
return reverseN(head, right)[0]
head.next = self.reverseBetween(head.next, left-1, right-1)
return head
- 迭代
方法一:
找到pre 和 suc, 然后用上文的方法把中间反转然后连起来
class Solution:
def reverseBetween(self, head: ListNode, left: int, right: int) -> ListNode:
dummy = ListNode(-1)
dummy.next = head
p = dummy
pos = 1
while pos < left:
p = p.next
pos += 1
# 现在p在需要被反转的头节点的前一位 pre
pre = p
while pos <= right + 1:
p = p.next
pos += 1
# 现在p在需要被反转的链表的后一位 suc
suc = p
prev = suc
cur = pre.next
while cur is not suc:
next = cur.next
cur.next = prev
prev = cur
cur = next
pre.next = prev
return dummy.next
方法二: 一次遍历「穿针引线」反转链表(头插法)
方法一的缺点是:如果 left 和 right 的区域很大,恰好是链表的头节点和尾节点时,找到 left 和 right 需要遍历一次,反转它们之间的链表还需要遍历一次,虽然总的时间复杂度为 O(N),但遍历了链表 2 次,可不可以只遍历一次呢?答案是可以的。我们依然画图进行说明。
class Solution:
def reverseBetween(self, head: ListNode, left: int, right: int) -> ListNode:
dummy = ListNode(-1)
dummy.next = head
p = dummy
for _ in range(left - 1):
p = p.next
prehead = p
prev = prehead.next
cur = prev.next
for _ in range(right - left):
next = cur.next
cur.next = prehead.next 这里要注意
prehead.next = cur
prev.next = next
cur = next
return dummy.next
3. 如何 K 个一组反转链表
25.K个一组翻转链表(困难)
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def reverseKGroup(self, head: ListNode, k: int) -> ListNode:
def reverseList(head: ListNode, tail: ListNode):
prev = tail
cur = head
while cur != tail:
next = cur.next
cur.next = prev
prev = cur
cur = next
newhead = prev
newtail = head
return newhead, newtail
p = head
dummy = ListNode(-1)
dummy.next = head
lasttail = dummy
while head:
for _ in range(k):
if not head:
return dummy.next
head = head.next
nexthead = head
curhead = lasttail.next
newhead, newtail = reverseList(curhead, nexthead)
lasttail.next = newhead
lasttail = newtail
return dummy.next
4. 如何判断回文链表
这道题的关键在于,单链表无法倒着遍历,无法使用双指针技巧。
- 递归方法:
可以借鉴树的后序遍历,链表也来一个后序遍历不就行了
void traverse(TreeNode root) {
// 前序遍历代码
traverse(root.left);
// 中序遍历代码
traverse(root.right);
// 后序遍历代码
}
链表的可以写成
void traverse(ListNode head) {
// 前序遍历代码
traverse(head.next);
// 后序遍历代码
}
class Solution:
def isPalindrome(self, head: ListNode) -> bool:
left = head
def traverse(right):
if not right:
return True
nonlocal left
res = traverse(right.next)
res = res and right.val == left.val
left = left.next
return res
return traverse(head)
方法二:
那么最简单的办法就是,快慢指针找到中点然后反转后面的链表, 然后比较.
有几点需要注意
- 链表有奇数个和偶数个时, 慢指针的位置不同
1->2->2(slow)->1, 1->2->3(slow)->2->1. 当为奇数个的时候需要再把slow向右移动一位 - 下面的代码有个问题就是原链表被改动了,如何能复原链表呢. 可以看下面的代码.
class Solution:
def isPalindrome(self, head: ListNode) -> bool:
def reverseList(head):
prev = None
cur = head
while cur:
next = cur.next
cur.next = prev
prev = cur
cur = next
return prev
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if fast: #总共有奇数 1->2->3->2->1
slow = slow.next
right = reverseList(slow)
left = head
while right:
if right.val != left.val:
return False
right = right.next
left = left.next
return True
class Solution:
def isPalindrome(self, head: ListNode) -> bool:
def reverseList(head):
prev = None
cur = head
while cur:
next = cur.next
cur.next = prev
prev = cur
cur = next
return prev
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if fast: #总共有奇数 1->2->3->2->1
slow = slow.next
righthead = reverseList(slow)# 注意区别
right = righthead
left = head
while right and right.next: # 注意区别
if right.val != left.val:
return False
right = right.next
left = left.next
if right and left.val != right.val:# 注意区别
return False# 注意区别
left.next = reverseList(righthead)# 注意区别
return True