原题描述
LRU和LFU这两种经典的题目,相信很多同学在面试的时候都有遇到过。今年秋招,我有个师兄,本身学历经历表达能力都是拉满的,他简历上有个项目用到的技术里写了LRU,结果好几个大厂的面试都考了这题,这真的是说明了,如果面试官想要你,在算法编程题是不会为难你的,如果不要你,直接给你一道冷门的Hard题。
LRU和LFU都可以认为是一种内存淘汰算法,在操作系统的分页内存管理、Redis缓存、Mysql缓存等场景都有广泛的应用。他们都是一种策略,在面对内存有限的情况下,需要把一些可能将来不会被访问的内存淘汰出去,而这两种算法的区别就是如何定义将来不会被访问的内存。对于LRU,认为最长时间没有被访问的元素是应该被淘汰的,而LFU则认为访问次数频率最小的元素应该是要被淘汰的。事实上,在真正的应用场景,往往会结合这两种算法来设计内存淘汰算法。
两题leetcode链接:
LRU
思路
这类题目的主要难点在于如何使得Get和Set的操作都是 O ( 1 ) O(1) O(1)的时间复杂度。其实,看到这种要求的复杂度,往往需要用到哈希表。通过哈希表来保存key和其对应的链表节点,然后再维护一条双向链表,便于删除和插入操作。那么思路其实就很明确了。
代码
// ListNode 表示一个双向链表中的一个节点
type ListNode struct {
Key int
Val int
Freq int
Next *ListNode
Prev *ListNode
}
func NewListNode(key, val int) *ListNode {
return &ListNode{
Key: key, Val: val}
}
// List 定义一个双向链表
type List struct {
Head *ListNode
Tail *ListNode
}
// NewList 创建一个空的双向链表,并且使用伪头部和伪尾部
func NewList() *List {
head := NewListNode(-1, -1)
tail := NewListNode(-1, -1)
head.Next = tail
tail.Prev = head
return &List{
Head: head, Tail: tail}
}
// AppendNodeHead 将某个节点插在链表头部
func (l *List) AppendNodeHead(node *ListNode) {
node.Next = l.Head.Next
l.Head.Next.Prev = node
node.Prev = l.Head
l.Head.Next = node
}
// DeleteNode 删除一个指定的节点
func (l *List) DeleteNode(node *ListNode) {
// 避免panic
if node.Prev

最低0.47元/天 解锁文章
1854

被折叠的 条评论
为什么被折叠?



