链表
160. 相交链表
找到A和B链表相交的部分,并返回相交部分
方法一:使用set存储节点的地址
(1)先逐步存储链表A的节点(实际上就是存储节点地址,这样子在对比地址空间时,可以找到真正相交的地方,而不会受到其他值相同的节点的影响
(2)逐步遍历B,对比B中每个节点与set中的地址
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> Optional[ListNode]:
set_a = set()
while headA:
set_a.add(headA) # 存的是节点, 而不是节点的值!!!
headA = headA.next
while headB:
if headB in set_a:
return headB
headB = headB.next
return None
方法二:双指针
(1)由于链表的特性,两个链表相交部分一定是在链表的后半部分
(2)于是,将AB补全为等长,公共部分就会在补全后两个链表的同一个位置
首先是两个链表(约定,值相同代表同一节点,0 代表空节点)
A表:[1, 2, 3, 7, 8, 9] B表:[4, 5, 7, 8, 9]
连接两个链表(表与表之间用 0 隔开)
AB表:[1, 2, 3, 7, 8, 9, 0, 4, 5, 7, 8, 9, 0]
BA表:[4, 5, 7, 8, 9, 0, 1, 2, 3, 7, 8, 9, 0]
观察连接后的两个表,可以发现相交的部分整齐的排列在末尾, 只需要逐个比较这两张表的节点,就能找到相交的起始位置。如果没有相交会如何?会陷入死循环吗? A表:[1, 2, 3] B表:[4, 5]
连接两个链表(表与表之间用 0 隔开) AB表:[1, 2, 3, 0, 4, 5, 0] BA表:[4, 5, 0, 1, 2, 3, 0]
最终AB和BA都为空,跳出循环
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> Optional[ListNode]:
A,B = headA,headB
while A != B:
A = A.next if A else headB
B = B.next if B else headA
return A
206. 反转链表
头插法
第一步:
最后一步:
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
cur, pre = head, None
while cur:
tmp = cur.next
cur.next = pre
pre = cur
cur = tmp
return pre
234. 回文链表
方法一:暴力(遍历存储所有元素,在判断是否是回文)
class Solution:
def isPalindrome(self, head: Optional[ListNode]) -> bool:
l1 = []
while head:
l1.append(head.val)
head = head.next
l2 = l1[::-1]
if l1 == l2:
return True
return False
方法二: 快慢指针 + 反转部分链表
(1)slow 和 quick 同时走,slow每次走一步,quick每次走两步,这样子当quick到尾巴时(null),slow刚好处于链表的中间。中间存在两种情况:
1、链表长度为偶数时(不加上null):实际上slow是停在中间两个节点的后一个,即后一个回文串的开头,此时可以直接比较。
2、链表长度为奇数时:slow会停在前后两个回文串的中间,需要向后移动一个,再开始比较。
(2)使用 cur 和 pre 指针,跟在slow指针后面反转前半部分链表
(3)从中间开始,向两端逐个比较元素是否相同
class Solution:
def isPalindrome(self, head: Optional[ListNode]) -> bool:
slow, quick = head, head
cur, pre = head, None
while quick and quick.next:
quick = quick.next.next
slow = slow.next
tmp = cur.next
cur.next = pre
pre = cur
cur = tmp
# quick不为None,说明是奇数的情况,需要后移一位
if quick:
slow = slow.next
while slow and pre and (slow.val == pre.val):
slow = slow.next
pre = pre.next
# 如果是回文,则最终都是None
if slow or pre:
return False
return True
141. 环形链表
快慢指针(或用哈希,比较地址空间是否重复)
class Solution:
def hasCycle(self, head: Optional[ListNode]) -> bool:
# 只有一个节点或为空时,不可能有环
if not head or not head.next:
return False
slow, fast = head, head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if fast is slow:
return True
return False
142. 环形链表 II
题目:给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
快慢指针(或用哈希,返回地址空间重复的第一个位置)
a-c=(k-1)(b+c)
,表示slow和fast相遇后,slow距离入口还有c
步。
当head和slow同时走c
步,此时head与入口的距离为a-c
,而slow正好在入口
根据a-c=(k-1)(b+c)
,head走完a-c
步后一定会在入口与slow相遇
考虑最坏情况,slow进入循环时,fast在slow下一位。
此时根据相对速度(slow走一步,fast走两步 <—> slow不动,fast走一步)
fast要走(环长-1步)才能与slow相遇。
class Solution:
def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
slow, fast = head, head
while fast and fast.next:
fast = fast.next.next
slow = slow.next
# slow,fast相遇后. slow 和 head一起走
if slow == fast:
while slow != head:
slow = slow.next
head = head.next
return slow
return None
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:
head, new_ls = list1,list1
list1 = list1.next
else:
head,new_ls = list2,list2
list2 = list2.next
while list1 and list2:
if list1.val < list2.val:
new_ls.next = list1
list1 = list1.next
else:
new_ls.next = list2
list2 = list2.next
new_ls = new_ls.next
new_ls.next = list1 if list1 else list2
return head
方法二:不使用额外链表
class Solution:
def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]:
if not list1:
return list2
if not list2:
return list1
head = ListNode(-1)
cur = head
while list1 and list2:
if list1.val < list2.val:
cur.next = list1
list1 = list1.next
else:
cur.next = list2
list2 = list2.next
cur = cur.next
cur.next = list1 if list1 else list2
return head.next
方法三:递归
class Solution:
def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]:
if not list1:
return list2
elif not list2:
return list1
elif list1.val < list2.val:
list1.next = self.mergeTwoLists(list1.next, list2)
return list1
else:
list2.next = self.mergeTwoLists(list1, list2.next)
return list2
2.两数相加
由于链表是逆序的,所以只需顺序遍历,计算每一位是多少,是否有进位
class Solution:
def addTwoNumbers(self, l1: Optional[ListNode], l2: Optional[ListNode]) -> Optional[ListNode]:
l3 = ListNode(-1)
head = l3
ten = 0 # 是否进位
while l1 or l2 or ten:
ten += (l1.val if l1 else 0) + (l2.val if l2 else 0)
l3.next = ListNode(ten % 10)
ten //= 10
l3 = l3.next
if l1:
l1 = l1.next
if l2:
l2 = l2.next
return head.next
19. 删除链表的倒数第 N 个结点
题目:给你一个链表,删除链表的 倒数第n个结点
,并且返回链表的头结点。
快慢指针:
假设链表总长为 a,倒数第n个节点,就是第 a - n + 1
个节点
(倒数第1个节点是第a个节点,倒数第2个节点是a-1……)
(1)slow,fast,dummy的 next 都是 head。
(2)fast先走n
步,此时 fast 距离最后一个节点的距离为 a - n
(3)slow和fast一起遍历到尾部(当fast.next为None是终止),此时slow走了a-n
步,而我们的目标a-n+1
就是slow.next
(4)删除slow.next
class Solution:
def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
# 创建dummy指向head,因为head有可能被删除
slow = fast = dummy = ListNode(next=head)
for _ in range(n):
fast = fast.next
while fast.next:
fast = fast.next
slow = slow.next # slow 停止的位置就是删除的前一个结点
slow.next = slow.next.next
return dummy.next
24. 两两交换链表中的节点
方法一:迭代(交换tmp节点的后两个节点)
class Solution:
def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]:
dummy = ListNode()
dummy.next = head
cur = dummy
while cur.next and cur.next.next:
node1 = cur.next
node2 = cur.next.next
cur.next = node2
node1.next = node2.next
node2.next = node1
cur = node1
return dummy.next
方法二:递归
(1)每次交换相邻节点(node1和node2)
(2)交换后,node2成为新的头节点,node1.next,需要node3与node4交换后才可以确定
class Solution:
def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]:
if not head or not head.next:
return head
node1 = head
node2 = node1.next
node3 = node2.next
node2.next = node1
node1.next = self.swapPairs(node3)
return node2
25. K 个一组翻转链表
每k个,执行一次翻转部分链表
class Solution:
# 翻转一段链表
def reverse(self, head:ListNode, tail: ListNode):
pre = tail.next
cur = head
while pre != tail: # 说明当前段已经翻转完
tmp = cur.next
cur.next = pre
pre = cur
cur = tmp
return tail,head # 头尾互换
def reverseKGroup(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:
dummy = ListNode()
dummy.next = head
pre = dummy
while head:
tail = pre
for _ in range(k):
tail = tail.next
if not tail:
return dummy.next
tail_next = tail.next
new_head, new_tail = self.reverse(head, tail)
# 接上翻转后的部分链表
pre.next = new_head
new_tail.next = tail_next
pre = new_tail
head = new_tail.next
return dummy.next
138. 随机链表的复制
复制拥有random节点的链表
存在的问题,random节点指向位置可能还没复制到新的链表上
解决思路,使用链表
class Solution:
def copyRandomList(self, head: 'Optional[Node]') -> 'Optional[Node]':
if not head: return
d = dict()
cur = head
while cur:
d[cur] = Node(cur.val)
cur = cur.next
cur = head
while cur:
d[cur].next = d.get(cur.next)
d[cur].random = d.get(cur.random)
cur = cur.next
return d[head]
148. 排序链表
归并排序
(一)递归版
(1)快慢指针找中点,切分左右区间
(2)递归排序左右区间
(3)合并排序好的左右区间
class Solution:
def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]:
if not head or not head.next:
return head
# 1、通过快慢指针找链表中点(奇数,slow停在中点;偶数,slow停在左侧中点)
slow, fast = head, head
while fast.next and fast.next.next:
fast = fast.next.next
slow = slow.next
mid, slow.next = slow.next, None # 切断两个区间
# 2、归并排序左右区间
left, right = self.sortList(head), self.sortList(mid)
new_head = cur = ListNode(0)
# 3、合并左右区间 <==> 合并两个有序链表
while left and right:
if left.val < right.val:
cur.next = left
left = left.next
else:
cur.next = right
right = right.next
cur = cur.next
cur.next = left if left else right
return new_head.next
(二)递推版
23. 合并 K 个升序链表
方法一:最小堆
维护一个堆集合,里面包括当前值可能最小的节点,每次将堆中最小元素加入到链表中
(1)初始时,最小元素集合为所有链表的头节点
(2)之后每次pop堆顶,并加入最小节点的next(此时next是该剩余链表的头节点)
(3)最小堆就是,当前还未加入新链表的所有剩余链表的头节点
from heapq import heapify, heappush, heappop
# __lt__定义了对象小于(less than)的比较行为
# 当使用 < 运算符来比较两个对象时, Python解释器会自动调用这个方法
# 定义该方法, 使得堆可以根据节点的val来构建
ListNode.__lt__ = lambda a, b: a.val < b.val
class Solution:
def mergeKLists(self, lists: List[Optional[ListNode]]) -> Optional[ListNode]:
cur = dummy = ListNode()
# 把所有的头节点都加入堆中
# 因为所有都是有序的, 所以第一个最小值在这些头节点中
h = [head for head in lists if head]
heapify(h)
while h:
node = heappop(h) # 当前最小节点
cur.next = node
cur = cur.next
if node.next:
heappush(h, node.next)
return dummy.next
方法二:分治(类似归并排序,递归排序左右区间,最后融合)
class Solution:
# 合并两个有序链表
def merge_two(self, list1, list2):
cur = dummy = ListNode()
while list1 and list2:
if list1.val < list2.val:
cur.next = list1
list1 = list1.next
else:
cur.next = list2
list2 = list2.next
cur = cur.next
cur.next = list1 if list1 else list2
return dummy.next
def mergeKLists(self, lists: List[Optional[ListNode]]) -> Optional[ListNode]:
m = len(lists)
if m == 0:
return None
if m == 1:
return lists[0]
left = self.mergeKLists(lists[:m//2])
right = self.mergeKLists(lists[m//2:])
return self.merge_two(left, right)
146. LRU 缓存
LRU:最近最少使用。 当缓存满的时候,移除最近最少使用的元素。
- get(k):如果cache中存在k,则返回k对应的v,并且将k移动到头部(表示最近使用了)
- put(k):如果k已经在cache中,那就更新k对应的v,并将k移动到头部。
– 如果k不在,先检查cache是否满了,满了先移除尾部元素,在把k加到头部
双向循环链表+哈希表
(1)双向循环:添加表头,删除表尾都是O(1)
(2)哈希表:快速找到指定节点
class Node:
# __slots__限制类的实例只能有特定的属性
# Python 默认是用 dict 存储属性的,每次用 . 访问属性都需要查字典。
# 如果声明 __slots__ 就不会创建字典,而是改用指针偏移量直接拿到属性对象。
# 所以既节省了内存(没有字典)又节省了时间(省去查字典的过程)。
__slots__ = 'pre', 'next', 'key', 'value'
def __init__(self, key=0, value=0):
self.key = key
self.value = value
class LRUCache:
def __init__(self, capacity: int):
self.capacity =capacity
self.dummy = Node()
self.dummy.pre = self.dummy
self.dummy.next = self.dummy
self.key2node = dict()
def get_node(self, key: int) -> Optional[Node]:
if key not in self.key2node:
return None
node = self.key2node[key]
# 从原位置移动到头部
self.remove(node)
self.push_front(node)
return node
def get(self, key: int) -> int:
node = self.get_node(key)
return node.value if node else -1
def put(self, key: int, value: int) -> None:
node = self.get_node(key)
# key存在cache中,更新val
if node:
node.value = value
return
# key不在cache中, 添加到cache头部
self.key2node[key] = node = Node(key, value)
self.push_front(node)
# cache满了
if len(self.key2node) > self.capacity:
back_node = self.dummy.pre # 获取最少使用的节点
del self.key2node[back_node.key]
self.remove(back_node)
def remove(self, x:Node) -> None:
x.pre.next = x.next
x.next.pre = x.pre
def push_front(self, x:Node) -> None:
x.pre = self.dummy
x.next = self.dummy.next
self.dummy.next.pre = x
self.dummy.next = x