目录
字典是通过 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();
}
}
};
参考链接: