剑指 Offer II 030. 插入、删除和随机访问都是 O(1) 的容器(数组+map)
设计一个支持在平均 时间复杂度 O(1) 下,执行以下操作的数据结构:
insert(val):当元素 val 不存在时返回 true ,并向集合中插入该项,否则返回 false 。
remove(val):当元素 val 存在时返回 true ,并从集合中移除该项,否则返回 false 。
getRandom:随机返回现有集合中的一项。每个元素应该有 相同的概率 被返回。
示例 :
输入: inputs = [“RandomizedSet”, “insert”, “remove”, “insert”, “getRandom”, “remove”, “insert”, “getRandom”]
[[], [1], [2], [2], [], [1], [2], []]
输出: [null, true, false, true, 2, true, false, 2]
解释:
RandomizedSet randomSet = new RandomizedSet(); // 初始化一个空的集合
randomSet.insert(1); // 向集合中插入 1 , 返回 true 表示 1 被成功地插入
randomSet.remove(2); // 返回 false,表示集合中不存在 2
randomSet.insert(2); // 向集合中插入 2 返回 true ,集合现在包含 [1,2]
randomSet.getRandom(); // getRandom 应随机返回 1 或 2
randomSet.remove(1); // 从集合中移除 1 返回 true 。集合现在包含 [2]
randomSet.insert(2); // 2 已在集合中,所以返回 false
randomSet.getRandom(); // 由于 2 是集合中唯一的数字,getRandom 总是返回 2
class RandomizedSet {
private:
vector<int> nums;
unordered_map<int,int> indices; //{数字:位置}
public:
/** Initialize your data structure here. */
RandomizedSet() {
srand((unsigned)time(NULL));//初始化随机数发生器
}
/** Inserts a value to the set. Returns true if the set did not already contain the specified element. */
bool insert(int val) {
if(indices.count(val)){//返回1或0
return false;
}
int index = nums.size();
nums.push_back(val);
indices[val]=index;
return true;
}
/** Removes a value from the set. Returns true if the set contained the specified element. */
bool remove(int val) {
if(!indices.count(val)){
return false;
}
//用最后一个元素覆盖掉要删除的元素
int index = indices[val];
int last = nums.back();
nums[index]=last;
indices[last]=index;
nums.pop_back();
indices.erase(val);
return true;
}
/** Get a random element from the set. */
int getRandom() {
int randomIndex = rand()%nums.size();//rand()返回一个非负整数
return nums[randomIndex];
}
};
剑指 Offer II 032. 有效的变位词(哈希表)
给定两个字符串 s 和 t ,编写一个函数来判断它们是不是一组变位词(字母异位词)。
注意:若 s 和 t 中每个字符出现的次数都相同且字符顺序不完全相同,则称 s 和 t 互为变位词(字母异位词)。
示例 1:
输入: s = “anagram”, t = “nagaram”
输出: true
示例 2:
输入: s = “rat”, t = “car”
输出: false
示例 3:
输入: s = “a”, t = “a”
输出: false
class Solution {
public:
bool isAnagram(string s, string t) {
vector<int> count(26);
int n=s.size();
if(s==t) return false;
if(s.size()!=t.size()) return false;
for(int i=0;i<s.size();i++){
count[s[i]-'a']++;
count[t[i]-'a']--;
}
for(auto num:count){
if(num!=0) return false;
}
return true;
}
};
剑指 Offer II 031. 最近最少使用缓存(哈希表+双向链表)
运用所掌握的数据结构,设计和实现一个 LRU (Least Recently Used,最近最少使用) 缓存机制 。
实现 LRUCache 类:
LRUCache(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存
int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
void put(int key, int value) 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字-值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。
示例:
输入
[“LRUCache”, “put”, “put”, “get”, “put”, “get”, “put”, “get”, “get”, “get”]
[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]
输出
[null, null, null, 1, null, -1, null, -1, 3, 4]
解释
LRUCache lRUCache = new LRUCache(2);
lRUCache.put(1, 1); // 缓存是 {1=1}
lRUCache.put(2, 2); // 缓存是 {1=1, 2=2}
lRUCache.get(1); // 返回 1
lRUCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}
lRUCache.get(2); // 返回 -1 (未找到)
lRUCache.put(4, 4); // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3}
lRUCache.get(1); // 返回 -1 (未找到)
lRUCache.get(3); // 返回 3
lRUCache.get(4); // 返回 4
LRU 缓存机制可以通过哈希表辅以双向链表实现,我们用一个哈希表和一个双向链表维护所有在缓存中的键值对。
双向链表按照被使用的顺序存储了这些键值对,靠近头部的键值对是最近使用的,而靠近尾部的键值对是最久未使用的。
哈希表即为普通的哈希映射(HashMap),通过缓存数据的键映射到其在双向链表中的位置。
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/OrIXps/solution/zui-jin-zui-shao-shi-yong-huan-cun-by-le-p3c2/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
struct DLinkedNode {
int key, value;
DLinkedNode* prev;
DLinkedNode* next;
DLinkedNode(): key(0), value(0), prev(nullptr), next(nullptr) {}
DLinkedNode(int _key, int _value): key(_key), value(_value), prev(nullptr), next(nullptr) {}
};
class LRUCache {
private:
unordered_map<int, DLinkedNode*> cache;
DLinkedNode* head;
DLinkedNode* tail;
int size;
int capacity;
public:
LRUCache(int _capacity): capacity(_capacity), size(0) {
// 使用伪头部和伪尾部节点
head = new DLinkedNode();
tail = new DLinkedNode();
head->next = tail;
tail->prev = head;
}
int get(int key) {
if (!cache.count(key)) {
return -1;
}
// 如果 key 存在,先通过哈希表定位,再移到头部
DLinkedNode* node = cache[key];
moveToHead(node);
return node->value;
}
void put(int key, int value) {
if (!cache.count(key)) {
// 如果 key 不存在,创建一个新的节点
DLinkedNode* node = new DLinkedNode(key, value);
// 添加进哈希表
cache[key] = node;
// 添加至双向链表的头部
addToHead(node);
++size;
if (size > capacity) {
// 如果超出容量,删除双向链表的尾部节点
DLinkedNode* removed = removeTail();
// 删除哈希表中对应的项
cache.erase(removed->key);
// 防止内存泄漏
delete removed;
--size;
}
}
else {
// 如果 key 存在,先通过哈希表定位,再修改 value,并移到头部
DLinkedNode* node = cache[key];
node->value = value;
moveToHead(node);
}
}
void addToHead(DLinkedNode* node) {
node->prev = head;
node->next = head->next;
head->next->prev = node;
head->next = node;
}
void removeNode(DLinkedNode* node) {
node->prev->next = node->next;
node->next->prev = node->prev;
}
void moveToHead(DLinkedNode* node) {
removeNode(node);
addToHead(node);
}
DLinkedNode* removeTail() {
DLinkedNode* node = tail->prev;
removeNode(node);
return node;
}
};
剑指 Offer II 033. 变位词组(哈希表)
给定一个字符串数组 strs ,将 变位词 组合在一起。 可以按任意顺序返回结果列表。
注意:若两个字符串中每个字符出现的次数都相同,则称它们互为变位词。
示例 1:
输入: strs = [“eat”, “tea”, “tan”, “ate”, “nat”, “bat”]
输出: [[“bat”],[“nat”,“tan”],[“ate”,“eat”,“tea”]]
class Solution {
public:
vector<vector<string>> groupAnagrams(vector<string>& strs) {
unordered_map<string, vector<string> >map;//{"eat":"eat","tea"}
vector<vector<string> >res;
for(string &str : strs){
string key = str;
sort(key.begin(),key.end());
map[key].push_back(str);
}
for(auto it=map.begin(); it!=map.end();it++){
res.push_back(it->second);
}
return res;
}
};
剑指 Offer II 034. 外星语言是否排序(哈希表)
某种外星语也使用英文小写字母,但可能顺序 order 不同。字母表的顺序(order)是一些小写字母的排列。
给定一组用外星语书写的单词 words,以及其字母表的顺序 order,只有当给定的单词在这种外星语中按字典序排列时,返回 true;否则,返回 false。
示例 1:
输入:words = [“hello”,“leetcode”], order = “hlabcdefgijkmnopqrstuvwxyz”
输出:true
解释:在该语言的字母表中,‘h’ 位于 ‘l’ 之前,所以单词序列是按字典序排列的。
示例 2:
输入:words = [“word”,“world”,“row”], order = “worldabcefghijkmnpqstuvxyz”
输出:false
解释:在该语言的字母表中,‘d’ 位于 ‘l’ 之后,那么 words[0] > words[1],因此单词序列不是按字典序排列的。
示例 3:
输入:words = [“apple”,“app”], order = “abcdefghijklmnopqrstuvwxyz”
输出:false
解释:当前三个字符 “app” 匹配时,第二个字符串相对短一些,然后根据词典编纂规则 “apple” > “app”,因为 ‘l’ > ‘∅’,其中 ‘∅’ 是空白字符,定义为比任何其他字符都小(更多信息)。
解题思路
首先,将order里的字符串字母映射到实际字母表的顺序,然后将words里的单词转变成对应实际字母表的顺序,如果排序后仍然保持顺序不变,说明单词排序正确。
class Solution {
public:
bool isAlienSorted(vector<string>& words, string order) {
unordered_map<char,int> m;
for(int i=0;i<order.size();i++){
m[order[i]] = (char)(i+'a'); //m['w']=a,m['o']=b
}
vector<string> a,b;
for(int i=0;i<words.size();i++){
string t="";
for(int j=0;j<words[i].size();j++){
t += m[words[i][j]]; //t="abcd"
}
a.push_back(t);//a=["abce","abcde"]
b.push_back(t);
}
sort(a.begin(),a.end()); //a=["abcde","abce"]
for(int i=0;i<words.size();i++){
if(a[i]!=b[i]) return false;
}
return true;
}
};
剑指 Offer II 035. 最小时间差
给定一个 24 小时制(小时:分钟 “HH:MM”)的时间列表,找出列表中任意两个时间的最小时间差并以分钟数表示。
示例 1:
输入:timePoints = [“23:59”,“00:00”]
输出:1
示例 2:
输入:timePoints = [“00:00”,“23:59”,“00:00”]
输出:0
解题思路
先将时间排序,然后分别计算两两分钟差,还要计算首位时间的分钟差
class Solution {
public:
int getMinutes(string &t){
return (int(t[0]-'0')*10+int(t[1]-'0'))*60+int(t[3]-'0')*10+int(t[4]-'0');
}
int findMinDifference(vector<string>& timePoints) {
int ans = INT_MAX;
sort(timePoints.begin(),timePoints.end());
int t0Minutes = getMinutes(timePoints[0]);
int preMinutes = t0Minutes;
for(int i=1;i<timePoints.size();i++){
int minutes = getMinutes(timePoints[i]);
ans = min(ans,minutes-preMinutes);
preMinutes = minutes;
}
ans = min(ans,t0Minutes+1440-preMinutes);//首位时间的分钟差
return ans;
}
};
剑指 Offer II 036. 后缀表达式(栈)
根据 逆波兰表示法,求该后缀表达式的计算结果。
有效的算符包括 +、-、*、/ 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。
示例 1:
输入:tokens = [“2”,“1”,“+”,“3”,“*”]
输出:9
解释:该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<int> stack;
int n = tokens.size();
for(int i=0;i<n;i++){
string &token = tokens[i];
if(isNumber(token)){
stack.push(atoi(token.c_str()));//将string转成int
}
else{
int num2 = stack.top();
stack.pop();
int num1 = stack.top();
stack.pop();
switch(token[0]){
case '+':
stack.push(num1+num2);
break;
case '-':
stack.push(num1-num2);
break;
case '*':
stack.push(num1*num2);
break;
case '/':
stack.push(num1/num2);
break;
}
}
}
return stack.top();
}
bool isNumber(string &s){
return !(s=="+"||s=="-"||s=="*"||s=="/");
}
};
剑指 Offer II 037. 小行星碰撞(栈)
给定一个整数数组 asteroids,表示在同一行的小行星。
对于数组中的每一个元素,其绝对值表示小行星的大小,正负表示小行星的移动方向(正表示向右移动,负表示向左移动)。每一颗小行星以相同的速度移动。
找出碰撞后剩下的所有小行星。碰撞规则:两个行星相互碰撞,较小的行星会爆炸。如果两颗行星大小相同,则两颗行星都会爆炸。两颗移动方向相同的行星,永远不会发生碰撞。
示例 1:
输入:asteroids = [5,10,-5]
输出:[5,10]
解释:10 和 -5 碰撞后只剩下 10 。 5 和 10 永远不会发生碰撞。
示例 2:
输入:asteroids = [8,-8]
输出:[]
解释:8 和 -8 碰撞后,两者都发生爆炸。
示例 3:
输入:asteroids = [10,2,-5]
输出:[10]
解释:2 和 -5 发生碰撞后剩下 -5 。10 和 -5 发生碰撞后剩下 10 。
示例 4:
输入:asteroids = [-2,-1,1,2]
输出:[-2,-1,1,2]
解释:-2 和 -1 向左移动,而 1 和 2 向右移动。 由于移动方向相同的行星不会发生碰撞,所以最终没有行星发生碰撞。
class Solution {
public:
vector<int> asteroidCollision(vector<int>& asteroids) {
//入栈条件:1.栈为空 2.栈顶元素为负数,当前元素为正数 3.栈顶元素和当前元素同号
stack<int> stk;
for(int i=0;i<asteroids.size();){
if(asteroids[i]<0){
if(stk.empty()||stk.top()<0){
stk.push(asteroids[i]);
i++;
}
else if(-asteroids[i]==stk.top()){
stk.pop();
i++;
}
else if(-asteroids[i]>stk.top()){
stk.pop();
}
else i++;
}
else{
stk.push(asteroids[i]);
i++;
}
}
vector<int> res;
while(!stk.empty()){
res.insert(res.begin(),stk.top());
stk.pop();
}
return res;
}
};
剑指 Offer II 038. 每日温度(单调栈)
请根据每日 气温 列表 temperatures ,重新生成一个列表,要求其对应位置的输出为:要想观测到更高的气温,至少需要等待的天数。如果气温在这之后都不会升高,请在该位置用 0 来代替。
示例 1:
输入: temperatures = [73,74,75,71,69,72,76,73]
输出: [1,1,4,2,1,1,0,0]
示例 2:
输入: temperatures = [30,40,50,60]
输出: [1,1,1,0]
示例 3:
输入: temperatures = [30,60,90]
输出: [1,1,0]
class Solution {
public:
vector<int> dailyTemperatures(vector<int>& temperatures) {
//维护一个单调递减栈
int n=temperatures.size();
stack<pair<int,int> >stk;//pair记录温度和日期
vector<int> ans(n);
for(int i=0;i<n;i++){
while(!stk.empty()){
auto p = stk.top();
if(temperatures[i]<=p.first){//若小于,可直接入栈
break;
}
else{ //若大于 则出栈后继续比较
ans[p.second] = i-p.second;
stk.pop();
}
}
stk.push({temperatures[i],i});
}
return ans;
}
};
剑指 Offer II 041. 滑动窗口的平均值(队列)
给定一个整数数据流和一个窗口大小,根据该滑动窗口的大小,计算滑动窗口里所有数字的平均值。
实现 MovingAverage 类:
MovingAverage(int size) 用窗口大小 size 初始化对象。
double next(int val) 成员函数 next 每次调用的时候都会往滑动窗口增加一个整数,请计算并返回数据流中最后 size 个值的移动平均值,即滑动窗口里所有数字的平均值。
示例:
输入:
inputs = [“MovingAverage”, “next”, “next”, “next”, “next”]
inputs = [[3], [1], [10], [3], [5]]
输出:
[null, 1.0, 5.5, 4.66667, 6.0]
解释:
MovingAverage movingAverage = new MovingAverage(3);
movingAverage.next(1); // 返回 1.0 = 1 / 1
movingAverage.next(10); // 返回 5.5 = (1 + 10) / 2
movingAverage.next(3); // 返回 4.66667 = (1 + 10 + 3) / 3
movingAverage.next(5); // 返回 6.0 = (10 + 3 + 5) / 3
class MovingAverage {
public:
queue<int> q;
int len=0;
double sum = 0;
MovingAverage(int size) {
len = size;
}
double next(int val) {
q.push(val);
sum += val;
if(q.size()>len){
sum -= q.front();
q.pop();
}
return sum/q.size();
}
};
剑指 Offer II 042. 最近请求次数(队列)
写一个 RecentCounter 类来计算特定时间范围内最近的请求。
请实现 RecentCounter 类:
RecentCounter() 初始化计数器,请求数为 0 。
int ping(int t) 在时间 t 添加一个新请求,其中 t 表示以毫秒为单位的某个时间,并返回过去 3000 毫秒内发生的所有请求数(包括新请求)。确切地说,返回在 [t-3000, t] 内发生的请求数。
保证 每次对 ping 的调用都使用比之前更大的 t 值。
示例:
输入:
inputs = [“RecentCounter”, “ping”, “ping”, “ping”, “ping”]
inputs = [[], [1], [100], [3001], [3002]]
输出:
[null, 1, 2, 3, 3]
解释:
RecentCounter recentCounter = new RecentCounter();
recentCounter.ping(1); // requests = [1],范围是 [-2999,1],返回 1
recentCounter.ping(100); // requests = [1, 100],范围是 [-2900,100],返回 2
recentCounter.ping(3001); // requests = [1, 100, 3001],范围是 [1,3001],返回 3
recentCounter.ping(3002); // requests = [1, 100, 3001, 3002],范围是 [2,3002],返回 3
class RecentCounter {
public:
queue<int> q;
int ask=0;
RecentCounter() {
ask=0;
}
int ping(int t) {
q.push(t);
int min = t-3000;
while(q.front()<min){
q.pop();
}
return q.size();
}
};