LeetCode-460. LFU缓存

241 篇文章 1 订阅
210 篇文章 0 订阅

460. LFU缓存

难度困难49收藏分享切换为英文关注

通过次数

1,527

提交次数

4,552

题目描述

评论 (29)

题解(14)New

提交记录

设计并实现最不经常使用(LFU)缓存的数据结构。它应该支持以下操作:get 和 put

get(key) - 如果键存在于缓存中,则获取键的值(总是正数),否则返回 -1。
put(key, value) - 如果键不存在,请设置或插入值。当缓存达到其容量时,它应该在插入新项目之前,使最不经常使用的项目无效。在此问题中,当存在平局(即两个或更多个键具有相同使用频率)时,最近最少使用的键将被去除。

进阶:
你是否可以在 O(1) 时间复杂度内执行两项操作?

示例:

LFUCache cache = new LFUCache( 2 /* capacity (缓存容量) */ );

cache.put(1, 1);
cache.put(2, 2);
cache.get(1);       // 返回 1
cache.put(3, 3);    // 去除 key 2
cache.get(2);       // 返回 -1 (未找到key 2)
cache.get(3);       // 返回 3
cache.put(4, 4);    // 去除 key 1
cache.get(1);       // 返回 -1 (未找到 key 1)
cache.get(3);       // 返回 3
cache.get(4);       // 返回 4

 

转载思路(讲的非常好):

LRU与LFU算法的区别?

这道题是让我们实现最近不常用页面置换算法LFU (Least Frequently Used), 之前我们做过一道类似的题LRU Cache,让我们求最近最少使用页面置换算法LRU (Least Recnetly Used)。两种算法虽然名字看起来很相似,但是其实是不同的。顾名思义,LRU算法是首先淘汰最长时间未被使用的页面,而LFU是先淘汰一定时间内被访问次数最少的页面。光说无凭,举个例子来看看,比如说我们的cache的大小为3,然后我们按顺序存入 5,4,5,4,5,7,这时候cache刚好被装满了,因为put进去之前存在的数不会占用额外地方。那么此时我们想再put进去一个8,如果使用LRU算法,应该将4删除,因为4最久未被使用,而如果使用LFU算法,则应该删除7,因为7被使用的次数最少,只使用了一次。相信这个简单的例子可以大概说明二者的区别。

 

为什么我们需要用三个哈希表来作为存储结构?

这道题比之前那道LRU的题目还要麻烦一些,因为那道题只要用个list把数字按时间顺序存入,链表底部的位置总是最久未被使用的,每次删除底部的值即可。而这道题不一样,由于需要删除最少次数的数字,那么我们必须要统计每一个key出现的次数,所以我们用一个哈希表m来记录当前数据{key, value}和其出现次数之间的映射,这样还不够,为了方便操作,我们需要把相同频率的key都放到一个list中,那么需要另一个哈希表freq来建立频率和一个里面所有key都是当前频率的list之间的映射。由于题目中要我们在O(1)的时间内完成操作了,为了快速的定位freq中key的位置,我们再用一个哈希表iter来建立key和freq中key的位置之间的映射。最后当然我们还需要两个变量cap和minFreq,分别来保存cache的大小,和当前最小的频率。

 

详细例子请看:

附上作者原文地址:https://www.cnblogs.com/grandyang/p/6258459.html

 

我按照此博主的思路重新写的代码,有注释,应该能好理解一些。

#include<iostream>
#include<list>
#include<unordered_map>

using namespace std;
class LFUCache {
public:
	LFUCache(int capacity) {
		this->capacity = capacity;
	}

	int get(int key) {
		if (m_map.count(key) != 1) {
			return -1;
		}
		else {
			auto destAddr = m_iter[key];
			m_freq[m_map[key].freq].erase(destAddr);               //因为频率改变,所以需要在链表当中进行删除
			m_map[key].freq++;

			m_freq[m_map[key].freq].push_back(key);
			m_iter[key] = --m_freq[m_map[key].freq].end();

			if (m_freq[minFreq].size() == 0)
				++minFreq;

			return m_map[key].value;
		}
	}

	void put(int key, int value) {
		if (capacity <= 0)
			return;

		/* 说明目前不存在该值 */
		if (get(key) == -1) {
			/* 需要做一次弹出操作 */
			if (m_map.size() >= capacity) {
				auto minF = m_freq[minFreq];
				int frontKey = minF.front();              //求出需要删除的key
				m_freq[minFreq].pop_front();
				m_map.erase(frontKey);
				m_iter.erase(frontKey);
			}

			Node insNode;
			insNode.value = value;
			insNode.freq = 1;
			m_map[key] = insNode;
		    m_freq[insNode.freq].push_back(key);
			m_iter[key] = --m_freq[insNode.freq].end();

			minFreq = 1;
		}
		else {
			m_map[key].value = value;
		}

	}

private:
	int capacity;           //当前容量
	int minFreq;            //最小次数
	typedef struct node {
		int value;          //存取的value值
		int freq;           //存储的次数
	}Node;

	unordered_map<int,Node> m_map;                       //key值对应Node节点
	unordered_map<int,list<int>> m_freq;                 //key值对应Node节点当中的freq
	unordered_map<int,list<int>::iterator> m_iter;       //key值对应key值
};


int main() {

	LFUCache cache(2);

	cache.put(1, 1);
	cache.put(2, 2);
	cache.get(1);       // 返回 1
	cache.put(3, 3);    // 去除 key 2
	cache.get(2);       // 返回 -1 (未找到key 2)
	cache.get(3);       // 返回 3
	cache.put(4, 4);    // 去除 key 1
	int ret1 = cache.get(1);       // 返回 -1 (未找到 key 1)
	int ret2 = cache.get(3);       // 返回 3 
	int ret3 = cache.get(4);       // 返回 4

	cout << "ret1:" << ret1 << endl;
	cout << "ret2:" << ret2 << endl;
	cout << "ret3:" << ret3 << endl;


	system("pause");
	return 0;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值