146. LRU Cache

需要注意的地方:任意BiListNode实例不仅存在于双向链表中,还存在于**LRUcache**中,添加和删除的时候必须同步进行。

  1. class BiListNode:
  2. def __init__(self, key=0, value=0):
  3. self.key = key
  4. self.value = value
  5. self.next = None
  6. self.prev = None
  7. class LRUCache:
  8. def __init__(self, capacity: int):
  9. self.LRU = dict() # 存放key:BiListNode(key,value)的字典
  10. self.capacity = capacity
  11. self.size = 0
  12. self.head = BiListNode() # 避免空判断,伪头尾节点
  13. self.tail = BiListNode()
  14. self.head.next = self.tail # 靠近头部的为最近使用(get, put)的节点,反之亦然
  15. self.tail.prev = self.head
  16. def get(self, key: int) -> int:
  17. if key not in self.LRU:
  18. return -1
  19. node = self.LRU[key]
  20. self.delete_node(node)
  21. self.add_to_head(node)
  22. return node.value
  23. def put(self, key: int, value: int) -> None:
  24. if key in self.LRU:
  25. node = self.LRU[key]
  26. node.value = value # 存储的是节点,会一并更改
  27. self.delete_node(node)
  28. self.add_to_head(node)
  29. else:
  30. node = BiListNode(key, value)
  31. self.LRU[key] = node # 不要忘记加入LRU缓存!
  32. self.add_to_head(node)
  33. self.size += 1
  34. if self.size > self.capacity:
  35. tail_node = self.tail.prev
  36. self.delete_node(tail_node)
  37. self.LRU.pop(tail_node.key) # 不要忘记删除LRU缓存!
  38. self.size -= 1
  39. def delete_node(self, node):
  40. node.prev.next = node.next
  41. node.next.prev = node.prev
  42. def add_to_head(self, node):
  43. node.next, node.prev = self.head.next, self.head
  44. self.head.next = node
  45. node.next.prev = node
  • 时间复杂度:getput均为数据结构设计 - 图1
  • 空间复杂度:数据结构设计 - 图2

208. Implement Trie (Prefix Tree)

Approach I** 建立TrieNode节点
每个TrieNode节点拥有一个children数组,指向
26**个字母,is_end则代表是否为单词尾部。

  1. class TrieNode:
  2. def __init__(self):
  3. self.children = [None] * 26
  4. self.is_end = False
  5. class Trie:
  6. def __init__(self):
  7. self.root = TrieNode()
  8. def insert(self, word: str) -> None:
  9. node = self.root
  10. for ch in word:
  11. index = ord(ch) - ord('a')
  12. if not node.children[index]:
  13. node.children[index] = TrieNode()
  14. node = node.children[index]
  15. node.is_end = True
  16. def search(self, word: str) -> bool:
  17. node = self.root
  18. for ch in word:
  19. index = ord(ch) - ord('a')
  20. if not node.children[index]:
  21. return False
  22. node = node.children[index]
  23. return node.is_end
  24. def startsWith(self, prefix: str) -> bool:
  25. node = self.root
  26. for ch in prefix:
  27. index = ord(ch) - ord('a')
  28. if not node.children[index]:
  29. return False
  30. node = node.children[index]
  31. return True
  • 时间复杂度:初始化数据结构设计 - 图3,每次查询插入数据结构设计 - 图4数据结构设计 - 图5是字符串或前缀的长度
  • 空间复杂度:数据结构设计 - 图6数据结构设计 - 图7字符串长度之和数据结构设计 - 图8是字符集大小(26)

Approach II** 直接使用字典**

  1. class Trie:
  2. def __init__(self):
  3. self.lookup = {} # 查询字典(树的根节点)
  4. def insert(self, word: str) -> None:
  5. node = self.lookup
  6. for ch in word:
  7. if ch not in node:
  8. node[ch] = {}
  9. node = node[ch]
  10. node['#'] = '#' # 单词尾部标记
  11. def search(self, word: str) -> bool:
  12. node = self.lookup
  13. for ch in word:
  14. if ch not in node:
  15. return False
  16. node = node[ch]
  17. return True if '#' in node else False
  18. def startsWith(self, prefix: str) -> bool:
  19. node = self.lookup
  20. for ch in prefix:
  21. if ch not in node:
  22. return False
  23. node = node[ch]
  24. return True

440. K-th Smallest in Lexicographical Order

**字典树**应用题。
数据结构设计 - 图9
核心:按照字典序的定义,以i开头的所有数字必定位于以i+1开头的所有数字前面。

  1. class Solution:
  2. def findKthNumber(self, n: int, k: int) -> int:
  3. def count(cur) -> int:
  4. """
  5. 返回以cur开头的(包括)且 ≤n 的数字数量
  6. """
  7. cnt = 0
  8. low, high = cur, cur
  9. while low <= n:
  10. cnt += min(high, n) - low + 1
  11. low *= 10
  12. high = 10 * high + 9
  13. return cnt
  14. cur = 1
  15. k -= 1
  16. while k:
  17. if (cnt := count(cur)) <= k:
  18. k -= cnt
  19. cur += 1
  20. else:
  21. k -= 1
  22. cur *= 10
  23. return cur
  • 时间复杂度:数据结构设计 - 图10
  • 空间复杂度:数据结构设计 - 图11

295. Find Median from Data Stream

使用大根堆小根堆(Python默认)来实现。对于大根堆addget都需要取反

  1. class MedianFinder:
  2. def __init__(self):
  3. self.A = [] # 小根堆(优先存储,较大数)
  4. self.B = [] # 大根堆(较小数)
  5. self.N = 0 # 已存储的数字个数
  6. def addNum(self, num: int) -> None:
  7. if self.N & 1:
  8. heapq.heappush(self.B, -heapq.heappushpop(self.A, num))
  9. else:
  10. heapq.heappush(self.A, -heapq.heappushpop(self.B, -num))
  11. self.N += 1
  12. def findMedian(self) -> float:
  13. return self.A[0] if self.N & 1 else (self.A[0] - self.B[0]) / 2.0
  • 时间复杂度:find操作是数据结构设计 - 图12add操作(堆的插入与弹出)是数据结构设计 - 图13
  • 空间复杂度:数据结构设计 - 图14

Follow up

  • If all integer numbers from the stream are in the range [0, 100], how would you optimize your solution?

如果数据流中所有整数都在 0~100 范围内,那么我们可以利用计数排序统计每一类数的数量,并使用双指针维护中位数。

  • If 99% of all integer numbers from the stream are in the range [0, 100], how would you optimize your solution?

如果数据流中 99% 的整数都在 0~100 范围内,那么我们依然利用计数排序统计每一类数的数量,并使用双指针维护中位数。对于超出范围的数,我们可以单独进行处理,建立两个数组,分别记录小于 0 的部分的数的数量和大于 100 的部分的数的数量即可。当小部分时间,中位数不落在区间 [0,100] 中时,我们在对应的数组中暴力检查即可。