当前位置: 首页 > article >正文

大一计算机的自学总结:数据结构设计相关题

前言

说实在的,感觉这种设计数据结构的题比链表题还要ex,尤其是当哈希表和链表一起上的时候!

一、设计有setAll功能的哈希表

#include <bits/stdc++.h>
using namespace std;

int cnt=0,setAllTime=0,setAllValue;
map<int,pair<int,int> >mySet;

void put(int x,int y)
{
    mySet[x]={y,cnt};
}

void get(int x)
{
    map<int,pair<int,int> >::iterator iter=mySet.find(x);
    if(iter==mySet.end())
    {
        cout<<-1<<endl;
    }
    else
    {
        if(mySet[x].second<setAllTime)
        {
            cout<<setAllValue<<endl;
        }
        else 
        {
            cout<<mySet[x].first<<endl;
        }
    }
}

int main()
{
    int n;
    cin>>n;
    int opt,x,y;
    for(int i=0;i<n;i++)
    {
        cin>>opt;
        if(opt==1)
        {
            cin>>x>>y;
            put(x,y);
            cnt++;
        }
        else if(opt==2)
        {
            cin>>x;
            get(x);
        }
        else
        {
            cin>>x;
            setAllTime=cnt++;
            setAllValue=x;
        }
    }
}

这个题若是不要求O(1),setAll时直接全改一遍就行了。但这里要求时间复杂度为O(1),所以要想新的思路。

这里引入“时间戳”的方法。每加入一个元素,让cnt++,模拟时间。之后若是有setAll的操作,就记下此时的时间,只有在拿数据的时候,若是该元素的加入时间在setAllTime之前,才输出setAll时的值。这样直接对结果操作就避免了把每个数据刷一遍。

二、LRU 缓存

class LRUCache {
public:
    struct doubleList
    {
        int key;
        int val;
        doubleList* last;
        doubleList* next;
    };

    map<int,doubleList*>m;
    doubleList* head=NULL;
    doubleList* tail=NULL;
    int cap;
    
    LRUCache(int capacity) {
        cap=capacity;
    }

    void add(doubleList* node)
    {
        if(node==NULL)
        {
            return ;
        }
        if(head==NULL)
        {
            head=node;
            tail=head;
        }
        else
        {
            tail->next=node;
            node->last=tail;
            tail=tail->next;
        }
    }

    void move(doubleList* node)
    {
        if(node==tail)
        {
            return ;
        }
        if(node==head)
        {
            head=node->next;
            head->last=NULL;
        }
        else
        {
            node->last->next=node->next;
            node->next->last=node->last;
        }
        tail->next=node;
        node->last=tail;
        tail=tail->next;
    }

    doubleList* removeHead()
    {
        if(head==NULL)
        {
            return NULL;
        }
        doubleList* ans=head;
        if(head==tail)
        {
            head=NULL;
            tail=NULL;
        }
        else
        {
            head=ans->next;
            ans->next=NULL;
            head->last=NULL;
        }
        return ans;
    }
    
    int get(int key) {
        map<int,doubleList*>::iterator iter=m.find(key);
        if(iter!=m.end())
        {
            doubleList* node=iter->second;
            move(node);
            return node->val;
        }
        return -1;
    }
    
    void put(int key, int value) {
        map<int,doubleList*>::iterator iter=m.find(key);
        if(iter!=m.end())
        {
            doubleList* node=iter->second;
            node->val=value;
            move(node);
        }
        else
        {
            if(m.size()==cap)
            {
                doubleList* node=removeHead();
                m.erase(node->key);
            }
            doubleList* node=new doubleList(key,value);
            m[key]=node;
            add(node);
        }
    }
};

这个题就开始上难度了,因为要时刻记录操作早晚的信息,所以考虑使用双链表结构记录操作早晚的信息让早操作的在头节点,晚操作的在尾节点。

之后,还需要一个map来记录值和对应链表上节点的地址。之后每次从map上找对应的节点,然后每次操作都对双链表进行操作,不断更新操作时间顺序即可。

三、O(1) 时间插入、删除和获取随机元素

class RandomizedSet {
public:

    map<int,int>m;
    vector<int>arr;

    RandomizedSet() {
        srand(time(0));
    }
    
    bool insert(int val) {
        map<int,int>::iterator iter=m.find(val);
        if(iter==m.end())
        {
            arr.push_back(val);
            m[val]=arr.size()-1;
            return true;
        }
        return false;
    }
    
    bool remove(int val) {
        map<int,int>::iterator iter=m.find(val);
        if(iter!=m.end())
        {
            arr[m[val]]=arr[arr.size()-1];
            m[arr[arr.size()-1]]=m[val];
            arr.pop_back();
            m.erase(val);
            return true;
        }
        return false;
    }
    
    int getRandom() {
        return arr[rand()%arr.size()];      
    }
};

这个题要建个vector来存所有元素,方便实现随机获取。之后,为了实现删除元素之后还能保证vector里每个位置都存着有效值,所以删除时将vector最后的元素移到要删除元素的位置。为了实现查找每个元素的位置,所以要建个map来记录元素信息和存的位置。

四、O(1) 时间插入、删除和获取随机元素 - 允许重复

class RandomizedCollection {
public:
    RandomizedCollection() {
        srand(time(0));
    }
    
    map<int,set<int> >m;
    vector<int>arr;

    bool insert(int val) {
        map<int,set<int> >::iterator iter=m.find(val);
        arr.push_back(val);
        m[val].insert(arr.size()-1);
        if(iter==m.end())
        {
            return true;
        }
        return false;
    }
    
    bool remove(int val) {
        map<int,set<int> >::iterator iter=m.find(val);
        if(iter!=m.end())
        {
            int idx=*(m[val].begin());
            arr[idx]=arr.back();
            m[val].erase(idx);
            m[arr.back()].erase(arr.size()-1);
            if(idx<arr.size()-1)
            {
                m[arr[idx]].insert(idx);
            }
            if(m[val].empty())
            {
                m.erase(val);
            }
            arr.pop_back();
            return true;
        }
        return false;
    }
    
    int getRandom() {
        return arr[rand()%arr.size()];
    }
};

允许重复的话map里的值就不能只存一个位置,所以需要将map里的值部分改为一个set,来存这个键在vector里额度所有位置。

五、数据流的中位数

class MedianFinder {
public:

    priority_queue<int>small;
    priority_queue<int,vector<int>,greater<int> >big;

    MedianFinder() {
        
    }

    void balance()
    {
        if(big.size()-small.size()==2||small.size()-big.size()==2)
        {
            if(big.size()>small.size())
            {
                small.push(big.top());
                big.pop();
            }
            else
            {
                big.push(small.top());
                small.pop();
            }
        }
    }
    
    void addNum(int num) {
        if(big.empty()||big.top()<=num)
        {
            big.push(num);
        }
        else
        {
            small.push(num);
        }
        balance();
    }
    
    double findMedian() {      
        if( (big.size()+small.size() )%2==0)
        {
            return (double)(big.top()+small.top())/2;
        }
        else
        {
            return big.size()>small.size()?big.top():small.top();
        }
    }
};

这个题主要是思路很巧妙,分析中位数的特点,可以发现,中位数,比所有比它小的数都大,比所有比它大的数都小。所以,为了只对两部分的最大最小值操作,所以建立一个大根堆和一个小根堆。因为中位数的特点,所以让大根堆里放较小的那部分数,小根堆里放较大的那部分数。这样,在所有数都放入后,两个堆的堆顶就是中位数的位置。

六、最大频率栈

class FreqStack {
public:

    map<int,vector<int> >maxCnt;//最大频率栈
    map<int,int>valCnt;//查询表

    FreqStack() {
        
    }
    
    void push(int val) {
        if(valCnt.find(val)==valCnt.end())
        {
            valCnt[val]=1;
            maxCnt[1].push_back(val);
        }
        else
        {
            maxCnt[++valCnt[val]].push_back(val);
        }
    }
    
    int pop() {
        int n=maxCnt[maxCnt.size()].back();
        if(--valCnt[n]==0)
        {
            valCnt.erase(n);
        }
        maxCnt[maxCnt.size()].pop_back();
        if(maxCnt[maxCnt.size()].empty())
        {
            maxCnt.erase(maxCnt.size());
        }
        return n;
    }
};

这个题就是思路也难想,代码也难写。为了实现记录频率这个信息,所以设立一个map,键是频率,值是一个vector,用来记录所有频率为当前键的值。之后,还需要一个map来记录每个数的最大频率,也就是最大频率map的键,用来查找并输出对应值。

根据栈的特性先进后出,所以每次插入只要在对应vector上push_back和pop_back即可,输出只要输出vector.back()即可。并且,因为是栈结构,所以当频率增加时,不需要将此元素从最大频率map中删除,只需要加入更大频率里即可。

七、全 O(1) 的数据结构

class AllOne {
public:

    struct doubleList
    {
        int cnt;
        set<string>s;
        doubleList* last;
        doubleList* next;
    };

    doubleList* head=new doubleList(0);
    doubleList* tail= new doubleList(INT_MAX);

    map<string,doubleList*>wordCnt;

    AllOne() {
        head->s.insert("");
        tail->s.insert("");
        head->last=NULL;
        head->next=tail;
        tail->last=head;
        tail->next=NULL;
    }

    void insert_back(doubleList* cur,doubleList* node)
    {
        cur->next->last=node;
        node->next=cur->next;
        node->last=cur;
        cur->next=node;
    }

    void remove(doubleList* node)
    {
        node->last->next=node->next;
        node->next->last=node->last;
    }
    
    void inc(string key) {
        if(wordCnt.find(key)==wordCnt.end())
        {
            if(head->next->cnt!=1)
            {
                doubleList* node=new doubleList(1);
                node->s.insert(key);
                insert_back(head,node);
                wordCnt[key]=node;
            }
            else
            {
                head->next->s.insert(key);
                wordCnt[key]=head->next;
            }
        }
        else
        {
            doubleList* node=wordCnt[key];
            if(node->next->cnt!=node->cnt+1)
            {
                doubleList* p=new doubleList(node->cnt+1);
                p->s.insert(key);
                insert_back(node,p);
                wordCnt[key]=p;
            }
            else
            {
                node->next->s.insert(key);
                wordCnt[key]=node->next;
            }
            node->s.erase(key);
            if(node->s.empty())
            {
                remove(node);
            }
        }
    }
    
    void dec(string key) {
        doubleList* node=wordCnt[key];
        if(node->cnt==1)
        {
            wordCnt.erase(key);
        }
        else
        {
            if(node->last->cnt!=node->cnt-1)
            {
                doubleList* p=new doubleList(node->cnt-1);
                p->s.insert(key);
                insert_back(node->last,p);
                wordCnt[key]=p;
            }
            else
            {
                wordCnt[key]=node->last;
                node->last->s.insert(key);
            }
        }
        node->s.erase(key);
        if(node->s.empty())
        {
            remove(node);
        }
    }
    
    string getMaxKey() {
        return *tail->last->s.begin();
    }
    
    string getMinKey() {
        return *head->next->s.begin();
    }
};

因为有要求时间复杂度O(1),所以不能全遍历一遍,而且,因为不只是对最后加入的数进行操作,所以不能使用最大频率栈的思路,所以考虑使用双链表的结构。

首先,要设立头尾节点,只存空字符串,方便若没有东西时返回空字符串。之后,让双向链表存一个cnt一个set,来存频率为cnt的字符串。

之后的操作都是双向链表的,说难也不难,但说简单也不简单。

总结

yysy,虽然每写完一道数据结构设计题就有种再也不想碰这种题的感觉,但坚持下来确实对coding能力有不小的提升。

END


http://www.kler.cn/a/531631.html

相关文章:

  • ubuntu18.04环境下,Zotero 中pdf translate划线后不翻译问题解决
  • 四川正熠法律咨询有限公司正规吗可信吗?
  • 玉米苗和杂草识别分割数据集labelme格式1997张3类别
  • VoIP中常见术语
  • 前端力扣刷题 | 6:hot100之 矩阵
  • DeepSeek R1本地化部署 Ollama + Chatbox 打造最强 AI 工具
  • 浅谈知识蒸馏技术
  • 【玩转 Postman 接口测试与开发2_014】第11章:测试现成的 API 接口(下)——自动化接口测试脚本实战演练 + 测试集合共享
  • Immutable设计 SimpleDateFormat DateTimeFormatter
  • 如何用一年时间如何能掌握 C++ ?
  • lstm部分代码解释1.0
  • MySQL锁详解
  • 深入探究 Spring 中 FactoryBean 注册服务的实现与原理
  • 【智力测试——二分、前缀和、乘法逆元、组合计数】
  • 【C++】P5734 【深基6.例6】文字处理软件
  • 使用Walk()遍历目录
  • Mac电脑上好用的免费截图软件
  • 【Linux】进程状态和优先级
  • Vue.js组件开发-实现左侧浮动菜单跟随页面滚动
  • FreeRTOS学习笔记3:系统配置文件+任务创建和删除的API函数介绍
  • 实验十一 Servlet(二)
  • 重新刷题求职2-DAY1
  • 鸟哥Linux私房菜第四部分
  • 【文件上传】
  • webpack-编译原理
  • 基于SpringBoot的美食烹饪互动平台的设计与实现(源码+SQL脚本+LW+部署讲解等)