源头

力扣有这两个题, 146. LRU 缓存460. LFU 缓存,记录下两者的区别和实现。

区别

首先各自的概念,LRU是最近不使用的淘汰,LFU是最近用的频率少的淘汰。然后下面举个栗子,Capacity=3:

[1,2,1,2,1,2,1,2,3,4]

如果是LRU,插入4时淘汰的是1,1是最近不使用的。 如果是LFU,插入4时淘汰的是3,最小使用频次是1,频次1里面只有一个3,所以淘汰3。

实现

LRU

因为要求GET和PUT的时间复杂度都是O(1),所以GET查询肯定得用map,因为栈和队列没法查询中间元素,单链表删除需要遍历找到前驱节点,为了删除时能在O(1)完成,使用双向链表。

下面的Golang实现里,双向链表用的官方的标准库 container/list,标准库的Value是个 interface{} 类型,断言会有性能消耗,如果真有需求自己实现LRU,可以自己实现双向链表省去断言的消耗。

package cache

import (
	"container/list"
	"log"
)

type Pair struct {
	Key, Value int
}

type LRUCache struct {
	Cap int
	L   *list.List
	M   map[int]*list.Element
}

func Constructor(capacity int) LRUCache {
	return LRUCache{
		L:   list.New(),
		M:   make(map[int]*list.Element, capacity),
		Cap: capacity,
	}
}

func (c *LRUCache) Get(key int) int {
	if c.M[key] == nil {
		return -1
	}
	ele := c.M[key]
	c.L.MoveToFront(ele)
	return ele.Value.(Pair).Value
}

func (c *LRUCache) Put(key int, value int) {
	if ele, ok := c.M[key]; ok {
		ele.Value = Pair{key, value}
		c.L.MoveToFront(ele)
	} else {
		ele := c.L.PushFront(Pair{
			key, value,
		})
		c.M[key] = ele
	}
	if c.L.Len() > c.Cap {
		if ele := c.L.Back(); ele != nil {
			if kv, ok := ele.Value.(Pair); ok {
				c.L.Remove(ele)
				delete(c.M, kv.Key)
			}
		}
	}
}

LFU

LFU相比LRU多了频次的逻辑。当容量满了的时候,如下:

  • Key的使用频次都不一样,淘汰频次最低的
  • Key的频次都一样,或者有两个以上的Key的频次都是最低的,需要按照LRU的处理办法,淘汰最近没有在用的

需要注意的是,Put新的Key时,要先 check capacity。如果先添加,后 check capacity,当容量满了,且热Key的频次都是大于1的,那么添加的新Key会在check时被删掉。

package cache

import (
	"container/list"
	"log"
)

type Pair struct {
	Key, Value int
	Frequency  int
}

type LFUCache struct {
	Cap  int
	Min  int
	MinL map[int]*list.List
	EleM map[int]*list.Element
}

func Constructor(capacity int) LFUCache {
	return LFUCache{
		Cap:  capacity,
		Min:  -1,
		MinL: make(map[int]*list.List),
		EleM: make(map[int]*list.Element),
	}
}

func (c *LFUCache) Get(key int) int {
	var (
		ele         *list.Element
		currentPair *Pair
	)

	ele = c.EleM[key]
	if ele == nil {
		return -1
	}

	currentPair = ele.Value.(*Pair)
	c.MinL[currentPair.Frequency].Remove(ele)
	currentPair.Frequency++
	c.EleM[key] = c.getMinList(currentPair.Frequency).PushFront(currentPair)

	if c.Min == currentPair.Frequency-1 && c.MinL[currentPair.Frequency-1].Len() == 0 {
		c.Min++
	}

	return currentPair.Value
}

func (c *LFUCache) getMinList(minIndex int) *list.List {
	if c.MinL[minIndex] == nil {
		c.MinL[minIndex] = list.New()
	}
	return c.MinL[minIndex]
}

func (c *LFUCache) Put(key int, value int) {
	if c.Cap == 0 {
		return
	}

	if ele, ok := c.EleM[key]; ok {
		pair := ele.Value.(*Pair)
		pair.Value = value
		c.Get(key)
	} else {
		if len(c.EleM) == c.Cap {
			if ele := c.MinL[c.Min].Back(); ele != nil {
				c.MinL[c.Min].Remove(ele)
				delete(c.EleM, ele.Value.(*Pair).Key)
			}
		}
		ele := c.getMinList(1).PushFront(&Pair{
			Key:       key,
			Value:     value,
			Frequency: 1,
		})
		c.EleM[key] = ele
		c.Min = 1
	}
}