文章目录

  • 哈希表
  • 概念
  • 疑问
  • 哈希表的应用
  • 哈希表的练习
  • 两个数组的交集
  • [两个数组的交集 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)