leetcode(12)链表系列题目
707. 设计链表
单链表
class Node:
def __init__(self, val):
self.val = val
self.next = None
class MyLinkedList:
def __init__(self):
self.head = Node(0) # 虚拟头部节点
self.size = 0 # 添加的节点数
def get(self, index: int) -> int:
if 0 <= index < self.size:
dummy = self.head
for _ in range(index + 1):
dummy = dummy.next
return dummy.val
else:
return -1
def addAtHead(self, val: int) -> None:
self.addAtIndex(0, val)
def addAtTail(self, val: int) -> None:
self.addAtIndex(self.size, val)
def addAtIndex(self, index: int, val: int) -> None:
if index < 0:
index = 0
elif index > self.size:
return
dummy = self.head
for _ in range(index):
dummy = dummy.next
node = Node(val)
node.next = dummy.next # 先把待插入位置后面的部分接到node后面
dummy.next = node # 再把node接到dummy后面
self.size += 1
def deleteAtIndex(self, index: int) -> None:
if 0 <= index < self.size:
dummy = self.head
for _ in range(index):
dummy = dummy.next
dummy.next = dummy.next.next
self.size -= 1
查找链表
160. 相交链表
同一题面试题 02.07. 链表相交
有的链表短,他走完了就去走另一条链表。如果有交点,他们最终一定会在同一个位置相遇
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
l1, l2 = headA, headB
while l1 != l2:
l1 = l1.next if l1 else headB
l2 = l2.next if l2 else headA
return l1
剑指 Offer 22. 链表中倒数第k个节点
注意:使用快慢指针
class Solution:
def getKthFromEnd(self, head: ListNode, k: int) -> ListNode:
fast, slow = head, head
for _ in range(k):
fast = fast.next
while fast:
fast = fast.next
slow = slow.next
return slow
876. 链表的中间结点
双指针
class Solution:
def middleNode(self, head: ListNode) -> ListNode:
if not head:
return head
slow, fast = head, head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
return slow
141. 环形链表
class Solution:
def hasCycle(self, head: Optional[ListNode]) -> bool:
slow, fast = head, head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
return True
return False
142. 环形链表 II
相遇时: slow指针走过的节点数为: x + y, fast指针走过的节点数:x + y + n (y + z),n为fast指针在环内走了n圈才遇到slow指针, (y+z)为 一圈内节点的个数A。
因为fast指针是一步走两个节点,slow指针一步走一个节点, 所以 fast指针走过的节点数 = slow指针走过的节点数 * 2:
(x + y) * 2 = x + y + n (y + z)
两边消掉一个(x+y): x + y = n (y + z)
因为要找环形的入口,那么要求的是x,因为x表示 头结点到 环形入口节点的的距离。
所以要求x ,将x单独放在左面:x = n (y + z) - y ,
再从n(y+z)中提出一个 (y+z)来,整理公式之后为如下公式:x = (n - 1) (y + z) + z 注意这里n一定是大于等于1的,因为 fast指针至少要多走一圈才能相遇slow指针。
当 n为1的时候,公式就化解为 x = z,意味着fast指针在环形里转了一圈之后,就遇到了 slow指针了。
即从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点。
class Solution:
def detectCycle(self, head: ListNode) -> ListNode:
slow, fast = head, head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
p = head
q = slow
while p != q:
p = p.next
q = q.next
return p
return None
反转链表
206.反转链表
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
pre, cur = None, head
while cur:
tmp = cur.next
cur.next = pre
pre = cur
cur = tmp
return pre
92. 反转链表 II
注意:设置 dummyNode
class Solution:
def reverseBetween(self, head: ListNode, left: int, right: int) -> ListNode:
# 设置 dummyNode 是这一类问题的一般做法
# dummy = ListNode(-1)
# dummy.next = head
dummy = ListNode(next = head) # 等价于
pre = dummy
for _ in range(left - 1):
pre = pre.next
cur = pre.next
for _ in range(left, right):
# for _ in range(right - left): # 等价于
tmp = cur.next
cur.next = tmp.next
tmp.next = pre.next
pre.next = tmp
return dummy.next
24. 两两交换链表中的节点
通过移动pre,cur 和 tmp 都在循环里面赋值
class Solution:
def swapPairs(self, head: ListNode) -> ListNode:
dummy = ListNode(next = head)
pre = dummy
while pre.next and pre.next.next: # 注意循环条件
cur = pre.next
tmp = pre.next.next
cur.next = tmp.next
tmp.next = cur
pre.next = tmp
pre = pre.next.next
return dummy.next
25. K 个一组翻转链表
注意:与 206.反转链表 的区别是需要判断节点是否够K个一组,并且要递归的调用翻转
class Solution:
def reverseKGroup(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:
cur = head
for _ in range(k):
if not cur:
return head # 如果head后续节点数不足k个直接返回head
cur = cur.next
pre, cur = None, head
for _ in range(k): # 将包括head往后的k个节点翻转
tmp = cur.next
cur.next = pre
pre = cur
cur = tmp
head.next = self.reverseKGroup(cur, k) # 递归得到下一段k个节点的反转后的头结点
# head已经是当前段k个节点的尾节点了,指向下一段的反转后的头结点
return pre
61. 旋转链表
步骤:
- 求链表长度;
- 找出倒数第 k+1 个节点;
- 链表重整:将链表的倒数第 k+1 个节点和倒数第 k 个节点断开,并把后半部分拼接到链表的头部。
class Solution:
def rotateRight(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:
if not head or not head.next: return head
n = 0
cur = head
while cur: # 步骤1
n += 1
cur = cur.next
k %= n # 注意要先取余之后再旋转
if k == 0: return head
fast, slow = head, head
while k: # 步骤2
fast = fast.next
k -= 1
while fast.next: # 注意是fast.next
fast = fast.next
slow = slow.next
newHead = slow.next # 步骤3
slow.next = None
fast.next = head
return newHead
86. 分隔链表
用两个dummy记录两个链表,一个链表放小于x的节点,一个链表放大于等于x的节点
最后,拼接这两个链表.
class Solution:
def partition(self, head: ListNode, x: int) -> ListNode:
p1 = dummy1 = ListNode(0)
p2 = dummy2 = ListNode(0)
# dummy1, dummy2 = ListNode(0), ListNode(0)
# p1 ,p2 = dummy1, dummy2 # 等价于
while head:
if head.val < x:
p1.next = head
p1 = p1.next
else:
p2.next = head
p2 = p2.next
head = head.next
p1.next = dummy2.next # 注意是dummy2.next而不是dummy2
p2.next = None
return dummy1.next
138. 复制带随机指针的链表
- 复制各节点到原始节点的后面,并拼接
- 构建各新节点的 random 指向
- 拆分两链表
class Solution:
def copyRandomList(self, head: 'Optional[Node]') -> 'Optional[Node]':
if not head:
return head
cur = head
while cur: # 步骤1
tmp = Node(cur.val)
tmp.next = cur.next
cur.next = tmp
cur = tmp.next
cur = head
while cur: # 步骤2
if cur.random:
cur.next.random = cur.random.next
cur = cur.next.next
pre, cur = head, head.next
res = cur # 用一个链表接住结果
while cur.next: # 步骤3,注意是cur.next
pre.next = pre.next.next
pre = pre.next
cur.next = cur.next.next
cur = cur.next
pre.next = None # 单独处理原链表尾节点
return res # 返回新链表头节点
143. 重排链表
简洁代码,取中,翻转,合并
class Solution:
def reorderList(self, head: ListNode) -> None:
if not head:
return
# 找到中点
slow, fast = head, head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
# 翻转后半
pre, cur = None, slow.next
slow.next = None
while cur:
tmp = cur.next
cur.next = pre
pre = cur
cur = tmp
# 交替合并
left, right = head, pre
while right:
tmp = left.next #注意这里是left.next
left.next = right
left = right
right =tmp
147. 对链表进行插入排序
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def insertionSortList(self, head: Optional[ListNode]) -> Optional[ListNode]:
if not head: return head
dummy = ListNode(0)
dummy.next = head
tmp = head
cur = head.next
while cur:
if tmp.val <= cur.val:
tmp = tmp.next
else:
pre = dummy
while pre.next.val <= cur.val:
pre = pre.next
tmp.next = cur.next # 先把后面连接好,再连前面的
cur.next = pre.next
pre.next = cur
cur = tmp.next
return dummy.next
删除链表元素
203. 移除链表元素
注意:设置 dummyNode,cur = dummy,判断cur.next
class Solution:
def removeElements(self, head: ListNode, val: int) -> ListNode:
dummy = ListNode(next = head)
cur = dummy
while cur.next:
if cur.next.val == val:
cur.next = cur.next.next
else:
cur = cur.next
return dummy.next
19. 删除链表的倒数第 N 个结点
注意:设置 dummyNode,fast, slow = head, dummy,快慢指针,先找到倒数第n个数
class Solution:
def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
dummy = ListNode(next = head)
fast, slow = head, dummy
for _ in range(n):
fast = fast.next
while fast:
fast = fast.next
slow = slow.next
slow.next = slow.next.next
return dummy.next
83. 删除排序链表中的重复元素
注意:不重复的时候继续往后遍历
class Solution:
def deleteDuplicates(self, head: ListNode) -> ListNode:
if not head:
return head
cur = head
while cur.next: # 需要后面一个不为空才能进行判断
if cur.val == cur.next.val:
cur.next = cur.next.next
else:
cur = cur.next # 继续往后遍历
return head
82. 删除排序链表中的重复元素 II
注意:设置 dummyNode
如果 cur.next 与 cur.next.next 对应的元素相同,就将 cur.next 以及所有后面拥有相同元素值的链表节点全部删除。
记下这个元素值 x,随后不断将 cur.next 从链表中移除,直到 cur.next 为空节点或者其元素值不等于 x 为止。此时,我们将链表中所有元素值为 x 的节点全部删除
class Solution:
def deleteDuplicates(self, head: ListNode) -> ListNode:
if not head:
return head
dummy = ListNode(next = head)
cur = dummy
while cur.next and cur.next.next:
if cur.next.val == cur.next.next.val:
x = cur.next.val
while cur.next and cur.next.val == x:
cur.next = cur.next.next
else:
cur = cur.next
return dummy.next
合并链表
21. 合并两个有序链表
class Solution:
def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]:
if not list1:
return list2
if not list2:
return list1
if list1.val < list2.val:
list1.next = self.mergeTwoLists(list1.next, list2)
return list1
else:
list2.next = self.mergeTwoLists(list1, list2.next)
return list2
23. 合并K个升序链表
思路 1:
归并,分而治之
链表两两合并
class Solution:
def mergeKLists(self, lists: List[Optional[ListNode]]) -> Optional[ListNode]:
if not lists:
return
n = len(lists)
return self.merge(lists, 0, n - 1)
def merge(self, lists, left, right):
if left == right:
return lists[left]
mid = (left + right) // 2
l = self.merge(lists, left, mid)
r = self.merge(lists, mid + 1, right)
return self.mergeTwo(l, r)
def mergeTwo(self, l1, l2):
if not l1:
return l2
if not l2:
return l1
if l1.val < l2.val:
l1.next = self.mergeTwo(l1.next, l2)
return l1
else:
l2.next = self.mergeTwo(l1, l2.next)
return l2
思路 2:
调用优先级队列,实现最小堆
时间复杂度:O(n∗log(k)),n 是所有链表中元素的总和,k 是链表个数。
class Solution:
def mergeKLists(self, lists: List[Optional[ListNode]]) -> Optional[ListNode]:
import heapq #调用堆
minHeap = []
for l in lists:
while l:
heapq.heappush(minHeap, l.val) #把l中的数据逐个加到堆中
l = l.next
dummy = ListNode(0)
p = dummy #构造虚节点
while minHeap:
val = heapq.heappop(minHeap) #依次弹出最小堆的数据
p.next = ListNode(val)
p = p.next
return dummy.next
剑指 Offer 18. 删除链表的节点
剑指 Offer 06. 从尾到头打印链表
剑指 Offer 35. 复杂链表的复制
参考资料:
从递归到迭代:由浅入深……