146.URL缓存机制
设计模式题,较为麻烦,但是理解后会更加深入理解规范的python
类是如何实现的。难点可能是在addToHead()
函数上,具体的理解根据代码参考以下图解。
注意,因为在形成②和③的时候要用到旧的node
,因此⑤这个链不能在②和③之前断掉,而形成④的时候会使其断掉,因此④要保证在②和③之后,其他顺序随意。
# 定义的一个双向链表类
class DLinkedNode:
def __init__(self, key=0, value=0):
self.key = key
self.value = value
self.prev = None
self.next = None
class LRUCache:
"""
越靠近head的结点进入缓存越晚,越靠近tail的进入缓存越早。
"""
def __init__(self, capacity: int):
# 保存缓存中的数字
self.cache = dict()
# 使用伪头部和伪尾部节点,因为涉及到在头部添加结点,所以这样方便
self.head = DLinkedNode()
self.tail = DLinkedNode()
# 首尾相连
self.head.next = self.tail
self.tail.prev = self.head
# 初始化最大容量和当前容量
self.capacity = capacity
self.size = 0
def get(self, key: int) -> int:
# dict()中查找元素的时间复杂度为O(1)
if key not in self.cache:
return -1
# 如果 key 存在,先通过哈希表定位,再移到头部
node = self.cache[key]
self.moveToHead(node)
return node.value
def put(self, key: int, value: int) -> None:
if key not in self.cache:
# 如果 key 不存在,创建一个新的节点
node = DLinkedNode(key, value)
# 添加进哈希表
self.cache[key] = node
# 添加至双向链表的头部
self.addToHead(node)
self.size += 1 # cache当前容量+1
if self.size > self.capacity:
# 如果超出容量,删除双向链表的尾部节点
removed = self.removeTail()
# 删除哈希表中对应的项
self.cache.pop(removed.key)
self.size -= 1
else:
# 如果 key 存在,先通过哈希表定位,再修改 value,并移到头部
node = self.cache[key]
node.value = value
self.moveToHead(node)
def addToHead(self, node):
# 具体参照草纸上的步骤来理解
node.prev = self.head
node.next = self.head.next
self.head.next.prev = node
self.head.next = node
def removeNode(self, node):
# 删除结点(不太明白的可以纸上画一下)
node.prev.next = node.next # 前一个结点的next是下一个结点
node.next.prev = node.prev # 下一个结点的prev是前一个结点
def moveToHead(self, node):
self.removeNode(node) # 删除旧的node
self.addToHead(node) # 将新的node添加到头部
def removeTail(self):
node = self.tail.prev # 定位要删除的node
self.removeNode(node) # 删除node
return node # 返回node以便更新self.cache
148.排序链表
本题最为纳闷的地方其实是:既然都要排序了,为什么不用数组非要用链表呢(人类迷惑行为)。不过既然出了,就给出两种解法:
解法一:自顶向下的归并排序
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def sortList(self, head: ListNode) -> ListNode:
"""
自顶向下的实现,在递归的时候需要保存各个中间变量,因此空间复杂度为O(logn)
"""
def find_mid(head, end):
# 开始的两个if定义递归出口
if not head: # 当head为空时返回
return head
# 当只有一个元素的时候,返回该元素(end一定为None)
if head.next == end:
head.next = None
return head
# 找到归并的mid来将大的链表一分为二
slow, fast = head, head
while fast != end:
slow = slow.next
fast = fast.next
if fast != end:
fast = fast.next
mid = slow
# 合并划分好的两个子链表
return merge(find_mid(head, mid), find_mid(mid, end))
def merge(head1, head2):
"""该函数用来合并两个链表,因为是递归的,所以head1和head2必定为有序的
所以该函数可以参考lc.合并两个有序链表 的实现
"""
dummyhead = temp = ListNode(0)
while head1 and head2:
if head1.val < head2.val:
temp.next = head1
head1 = head1.next
else:
temp.next = head2
head2 = head2.next
temp = temp.next
if head1:
temp.next = head1
if head2:
temp.next = head2
return dummyhead.next
return find_mid(head, None)
时间复杂度O(logn)
,空间复杂度O(logn)
。
解法二:自底向上的归并排序
思想与自顶向下的排序类似,区别是要多用几个指针来记录归并的过程(解法一中的递归更方便记录这个过程)。
class Solution:
def sortList(self, head: ListNode) -> ListNode:
def merge(head1: ListNode, head2: ListNode) -> ListNode:
# 普通的两有序链表的合并
dummyHead = ListNode(0)
temp, temp1, temp2 = dummyHead, head1, head2
while temp1 and temp2:
if temp1.val <= temp2.val:
temp.next = temp1
temp1 = temp1.next
else:
temp.next = temp2
temp2 = temp2.next
temp = temp.next
if temp1:
temp.next = temp1
elif temp2:
temp.next = temp2
return dummyHead.next
if not head:
return head
# 计算链表的总长度
length = 0
node = head
while node:
length += 1
node = node.next
dummyHead = ListNode(0, head)
subLength = 1
while subLength < length:
# prev用来连接合并好的两两子链表, curr用来遍历链表
prev, curr = dummyHead, dummyHead.next
# 依次合并连续的两个长度为subLength的链表
while curr:
# 获取第一个长度为subLength的链表
head1 = curr
for i in range(1, subLength):
if curr.next:
curr = curr.next
else:
break
# 获取第二个长度为subLength的链表
head2 = curr.next
curr.next = None # 第一个链表head1结尾置None
curr = head2
for i in range(1, subLength):
if curr and curr.next:
curr = curr.next
else:
break
succ = None # 用来迭代获得下两个长度为subLength的链表
if curr:
succ = curr.next
curr.next = None # 第二个链表head2结尾置None
merged = merge(head1, head2)
prev.next = merged # 将新的子链表合并到原来的上面
while prev.next:
prev = prev.next # prev指向当前合并完的最后一个元素
curr = succ # 更新curr以便下次遍历。
subLength <<= 1 # 子链表长度翻倍
return dummyHead.next
时间复杂度O(logn)
,因为只用到了几个指针来记录位置,所以空间复杂度O(1)
。
155.最小栈
使用单调栈help_
维护当前栈stack中的最小值,help_
栈顶的元素即为stack
中的最小值。需要注意的是,当最小值相等的情况也应该加入help_
中,省去了计数的麻烦。
class MinStack:
def __init__(self):
"""
initialize your data structure here.
"""
self.stack = [] # 存储当前栈中所有元素
self.help_ = [] # 维护一个单调栈,栈顶元素为当前stack中最小值
def push(self, x: int) -> None:
self.stack.append(x)
# 当出现更小元素的时候,加入辅助站help_中
#(注意相等的时候也要加入,否则还要计数处理相等的情况)
if not self.help_ or x <= self.help_[-1]:
self.help_.append(x)
def pop(self) -> None:
num = self.stack.pop()
# 如果弹出的元素正好是当前最小,则更新辅助站help_
if num == self.help_[-1]:
self.help_.pop()
def top(self) -> int:
return self.stack[-1]
def getMin(self) -> int:
# 辅助站栈顶元素即当前栈的最小值
return self.help_[-1]
# Your MinStack object will be instantiated and called as such:
# obj = MinStack()
# obj.push(x)
# obj.pop()
# param_3 = obj.top()
# param_4 = obj.getMin()