【算法系列(六)】:字典

目录

一、算法应用

13. 罗马数字转整数

 146. LRU缓存机制


字典是通过 Key/Value (键值对) 的形式来存放数据;该类最大的优点就是它查找元素的时间复杂度接近 O(1),实际项目中常被用来做一些数据的本地缓存,提升整体效率。

一、算法应用

13. 罗马数字转整数

  • 题目描述

罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。

字符          数值
I             1
V             5
X             10
L             50
C             100
D             500
M             1000

例如, 罗马数字 2 写做 II ,即为两个并列的 1。12 写做 XII ,即为 X + II 。 27 写做  XXVII, 即为 XX + V + II 。

通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII,而是 IV。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 IX。这个特殊的规则只适用于以下六种情况:

  • I 可以放在 V (5) 和 X (10) 的左边,来表示 4 和 9。
  • X 可以放在 L (50) 和 C (100) 的左边,来表示 40 和 90。 
  • C 可以放在 D (500) 和 M (1000) 的左边,来表示 400 和 900。

给定一个罗马数字,将其转换成整数。输入确保在 1 到 3999 的范围内。

示例 1:
输入:"III"
输出: 3

示例 2:
输入: "IV"
输出: 4

示例 3:
输入: "IX"
输出: 9

示例 4:
输入: "LVIII"
输出: 58
解释: L = 50, V= 5, III = 3.

示例 5:
输入: "MCMXCIV"
输出: 1994
解释: M = 1000, CM = 900, XC = 90, IV = 4.
  • 解题思路

利用字典的方式。把字典当作一个存储容器,key 存储罗马字符的所有组合,value 存储该组合代表的值。每次取一个字符,判断这个字符之后是否还有字符。如果有,则判断这两个字符是否在字典中,如果存在则取值。否则,按照一个字符去取值即可。

  • C++算法实现 
 int romanToInt(string s) {
	 map<string, int> dict = { {"I", 1}, {"II" , 2}, {"IV" , 4}, {"IX" , 9}, {"X" , 10}, {"XL" , 40}, {"XC" , 90},
	 {"C" , 100}, {"CD", 400}, {"CM" , 900}, {"V" , 5},{"L" , 50}, {"D" , 500}, {"M" , 1000} };

	 int res = 0;
	 int i = 0;
	 while (i < s.size()) {
		 string tmp(1,s[i]);
		 if (i + 1 < s.size()) {
			 if (dict.find(tmp+s[i+1]) != dict.end()) {
				 res += dict[tmp + s[i + 1]];
				 i += 2;
			 }
			 else {
				 res += dict[tmp];
				 i += 1;
			 }
			 
		 }
		 else {
			 res += dict[tmp];
			 i += 1;
			
		 }
		 
	 }

	 return res;
 }

 146. LRU缓存机制

  • 题目描述

运用你所掌握的数据结构,设计和实现一个  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
  • 解题思路

利用 字典 + 列表 的方式

计算机的缓存容量有限,如果缓存满了就要删除一些内容,给新内容腾位置。但问题是,删除哪些内容呢?我们肯定希望删掉哪些没什么用的缓存,而把有用的数据继续留在缓存里,方便之后继续使用。那么,什么样的数据,我们判定为「有用的」的数据呢?

LRU 缓存淘汰算法就是一种常用策略。LRU 的全称是 Least Recently Used,也就是说我们认为最近使用过的数据应该是是「有用的」,很久都没用过的数据应该是无用的,内存满了就优先删那些很久没用过的数据。

因为显然 cache 必须有顺序之分,以区分最近使用的和久未使用的数据;而且我们要在 cache 中查找键是否已存在;如果容量满了要删除最后一个数据;每次访问还要把数据插入到队头。

那么,什么数据结构同时符合上述条件呢?哈希表查找快,但是数据无固定顺序;链表有顺序之分,插入删除快,但是查找慢。所以结合一下,形成一种新的数据结构:哈希链表。

LRU 缓存算法的核心数据结构就是哈希链表,双向链表和哈希表的结合体。思想很简单,就是借助哈希表赋予了链表快速查找的特性嘛:可以快速查找某个 key 是否存在缓存(链表)中,同时可以快速删除、添加节点。回想刚才的例子,这种数据结构是不是完美解决了 LRU 缓存的需求?

把字典当作一个存储容器,由于字典是无序的,即 dict 内部存放的顺序和 key 放入的顺序是没有关系的,所以需要一个 list 来辅助排序。

  • C++算法实现
class LRUCache {
 public:
	 unordered_map<int, list<pair<int,int>>::iterator> hash_map;
	 list<pair<int,int>> head_cache;
	 int cap;

 public:
	 LRUCache(int capacity) {
		 cap = capacity;
	 }

	 int get(int key) {
		 if (hash_map.find(key) == hash_map.end()) {
			 return -1;
		 }

		 int val = hash_map[key]->second;
		 put(key, val);//利用put将数据提前

		 return val;
	 }

	 void put(int key, int value) {
		 pair<int, int> p(key, value);

		 if (hash_map.find(key) != hash_map.end()) {
			 head_cache.erase(hash_map[key]);//删除旧节点
			 head_cache.emplace_front(p);//插入到头部
			 hash_map[key] = head_cache.begin();//更新hash map
		 }
		 else {
			 if (cap == head_cache.size()) {
				 pair<int, int> last = head_cache.back();
				 head_cache.pop_back();//删除list最后一个数据
				 hash_map.erase(last.first);//删除map中的数据
			 }

			 //向头部添加数据
			 head_cache.emplace_front(p);
			 hash_map[key] = head_cache.begin();
		 }
	 }
 };

参考链接:

字典技术在求解算法题中的应用

LRU算法详解

猜你喜欢

转载自blog.csdn.net/wxplol/article/details/108481901
今日推荐