剑指offer哈希表+栈+队列专题

剑指 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();
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值