文章目录
- 哈希表
- 概念
- 疑问
- 哈希表的应用
- 哈希表的练习
- 两个数组的交集
- [两个数组的交集 II](https://leetcode-cn.com/problems/intersection-of-two-arrays-ii/)
- LRU缓存机制
哈希表
概念
- 1、哈希表是一种快速查找结构
- 2、经常用来存储“键值对”,key/value值
- 3、哈希表的查找时间近似为O(1),几乎可以瞬间查找到一个值
- 4、Java HashMap,Python的dict/set底层就是使用哈希表实现的,我们可以快速查找一个key的值
疑问
- 1、如果传入的值通过散列函数计算的下标一样怎么办?如何解决哈希冲突
- 1、链接法:
- 可以在当前位置挂一个链表,将冲突的数据都链接起来。
- 缺点:极端情况下一个链表可能会比较长
- 2、开放寻址法(Open Addressing):
- 线性探查(Linear Probing:f(i) = i)
- 二次方探查(Quadratic Probing:f(i) = i*i)
- 双重散列(Double Hashing:f(i) = i*hash2(elem))
- 3、重哈希:
- 上述两种方法不适合元素一直插入的情况,元素一直插入会导致底层数组不够用,使用重哈希方式,当数组使用到了一定比例(负载因子),会触发重哈希算法,重哈希会重新建立一个更大的数组,比如扩大两倍,然后把之前的数组元素重新放入到新的数组里
- 2、哈希表底层一般使用一个数组来实现。思考如何存一个k/v结构
- 3、通过对散列函数计算key的下标插入到对应位置
哈希表的应用
- 1、Python dict/set 底层就是使用哈希表实现
- 2、dict可以快速保存键值对
- 3、set常用来判断元素是否存在,set也可以使用dict来实现
哈希表的练习
- 掌握leetcode 哈希表和集合经典例题
- 掌握LRU cache (最近最少使用缓存算法)
两个数组的交集
- 思路:利用set的特性,熟悉set里面的方法使用
给定两个数组,编写一个函数来计算它们的交集。
示例 1:
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2]
示例 2:
输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[9,4]
class Solution(object):
def intersection(self, nums1, nums2):
"""
:type nums1: List[int]
:type nums2: List[int]
:rtype: List[int]
"""
return set(nums1) & set(nums2)
两个数组的交集 II
- 思路:
- 1、哈希表
- 2、指针排序(下面未实现)
给定两个数组,编写一个函数来计算它们的交集。
示例 1:
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2,2]
示例 2:
输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[4,9]
import collections
# 哈希表,时间复杂度O(max(n,m)),空间复杂度O(max(n,m))
class Solution:
def intersect(self, nums1, nums2):
if len(nums1) > len(nums2):
return self.intersect(nums2, nums1)
m = collections.Counter()
for num in nums1:
m[num] += 1
intersection = list()
for num in nums2:
if (m.get(num, 0)) > 0:
intersection.append(num)
m[num] -= 1
if m[num] == 0:
m.pop(num)
return intersection
LRU缓存机制
- 解决缓存空间不够用的一种常见的高效淘汰策略
- 思路:
- 1、题目说O(1)时间复杂度,说明空间复杂度不限,限定了容量。
- 2、使用字典存储key/value值,查询只需要根据关键字查找所以是O(1)
- 3、记录key对应的顺序,且要对key进行移位操作,使用**(循环)双向链表**,双向链表移位插入的时间复杂度为O(1)
- 4、Python内置的collections模块中的OrderedDict类底层是字典+循环双向链表,正好适合此题
运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和 写入数据 put 。
获取数据 get(key) - 如果关键字 (key) 存在于缓存中,则获取关键字的值(总是正数),否则返回 -1。
写入数据 put(key, value) - 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字/值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。
你是否可以在 O(1) 时间复杂度内完成这两种操作?示例:
LRUCache cache = new LRUCache( 2 /* 缓存容量 */ );
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // 返回 1
cache.put(3, 3); // 该操作会使得关键字 2 作废
cache.get(2); // 返回 -1 (未找到)
cache.put(4, 4); // 该操作会使得关键字 1 作废
cache.get(1); // 返回 -1 (未找到)
cache.get(3); // 返回 3
cache.get(4); // 返回 4
from collections import OrderedDict
class LRUCache(object):
def __init__(self, capacity:int):
"""
:type capacity: int
"""
self.od = OrderedDict()
self.capacity = capacity
def get(self, key):
"""
:type key: int
:rtype: int
"""
res = self.od.get(key)
if res:
self.od.move_to_end(key)
return res
else:
return -1
def put(self, key, value):
"""
:type key: int
:type value: int
:rtype: None
"""
self.od[key] = value
self.od.move_to_end(key)
if len(self.od) > self.capacity:
self.od.popitem(last=False)