zoukankan      html  css  js  c++  java
  • Leetcode 381. O(1) 时间插入、删除和获取随机元素

    1.题目描述

    设计一个支持在平均 时间复杂度 O(1) , 执行以下操作的数据结构。

    注意: 允许出现重复元素。

      1. insert(val):向集合中插入元素 val。
      2. remove(val):当 val 存在时,从集合中移除一个 val。
      3. getRandom:从现有集合中随机获取一个元素。每个元素被返回的概率应该与其在集合中的数量呈线性相关。

    示例:

    // 初始化一个空的集合。
    RandomizedCollection collection = new RandomizedCollection();
    
    // 向集合中插入 1 。返回 true 表示集合不包含 1 。
    collection.insert(1);
    
    // 向集合中插入另一个 1 。返回 false 表示集合包含 1 。集合现在包含 [1,1] 。
    collection.insert(1);
    
    // 向集合中插入 2 ,返回 true 。集合现在包含 [1,1,2] 。
    collection.insert(2);
    
    // getRandom 应当有 2/3 的概率返回 1 ,1/3 的概率返回 2 。
    collection.getRandom();
    
    // 从集合中删除 1 ,返回 true 。集合现在包含 [1,2] 。
    collection.remove(1);
    
    // getRandom 应有相同概率返回 1 和 2 。
    collection.getRandom();
    

    2.解题思路

    该题是之前那道Insert Delete GetRandom O(1)的拓展——允许插入重复的数字。

    解题思路和之前的一样,使用的数据结构有两个:

    (1)一个数组nums,用来保存每一个插入的val(可重复),与数组下标由一一对应的关系,便于getRandom返回时,每个元素被返回的概率应该与其在集合中的数量呈线性相关。

    (2)一个哈希结构,unordered_map<int, unordered_set<int>>,建立的是val值和val所有出现位置集合之间的哈希映射。

    只是写法略有不同,不同点如下:

    1. 由“一对一映射”变换成“一对多映射”;

                 因为有重复数字,不能像之前那样建立每个数字和其坐标的一对一映射,而是建立数字和其所有出现位置的集合之间的映射。

                 为了严格的遵守O(1)的时间复杂度,集合使用的unordered_set,其插入删除操作都是常量级的。

    “一对一映射”
    a——>1; //(a,1) b——>2; //(b,2)

    “一对多(集合)映射” (int ——> unordered_set<int>)
    a——>{1,3,5} ; //(a,1),(a,3),(a,5) b——>{2,4}; //(b,2),(b,4)

     

    2.对于insert函数,将要插入的数字val加入数组nums中,并且将val在数组中的 位置加入m[val]数组的末尾。另外,判断是否有重复只要看m[val]数组中val值的个数是1个还是多个。(m是一个上面提及的unordered_map对象,m[val]是unordered_set集合,本质上是数组实现的集合)

     

    3.对于remove函数,这是重难点。

    • 首先判断有无,如果哈希表有无val,没有直接返回false; 否则进行下一步;
    • 更新哈希表。取出nums的尾元素,把尾元素哈希表中的位置数组(集合,其中表征位置的数字是递增序列)中的最后一个元素更新为m[val]的尾元素,这样就可以删掉m[val]的尾元素了,如果m[val]只有一个元素,直接删除这个映射;
    • 对于数组nums,都是将数组最后一个位置的元素和要删除的元素交换位置,然后删除最后一个位置上的元素;(如果要删除的元素恰好是数组nums最后一个元素,直接删除即可。
    假定依次插入的值:
    (a,0)
    (b,1)      
    (a,2)        
    (a,3)
    (b,4)
    
    数组nums中:          哈希表中:
    [0,a]                       a——>{0,2,3}
    [1,b]
    [2,a]                       b——>{1,4}
    [3,a]                          
    [4,b]
    
    
    调用remove(a)结果是:(删除了(a,3))
    
    数组nums中:          哈希表中:
    [0,a]                       a——>{0,2}
    [1,b]
    [2,a]                       b——>{1,3}                        
    [3,b]
    

      

    3.示例代码

     

        
    class RandomizedCollection {
    public:
        /** Initialize your data structure here. */
        RandomizedCollection() {}
        
        
        /** Inserts a value to the set. Returns true if the set did not already contain the specified element. */
        bool insert(int val) {
            m[val].insert(nums.size());
            //nums.size()初始值为0,随着元素一个个插入nums,size++
            nums.push_back(val);
            return m[val].size()==1;   
            //val值的个数为1,返回true; val值的个数大于1,返回false
        }
    
        
        /** Removes a value from the set. Returns true if the set contained the specified element. */
        bool remove(int val) {
            //每当要删除一个值val,用nums的最后一个值last_val替换要删除的填,最后删除的nums的最后一个值
            //更新m[val]和m[last_val]两个集合中的值,m[val]最后一个元素弹出,同时将该元素替换m[last_val]中的最后的一个元素(删除最后一个,插入idx)
            if(m[val].empty()) return false;
            int idx = *m[val].begin();
            m[val].erase(idx);
            
            //要删除的不是nums的最后一个元素
            if(nums.size()-1 != idx){
                int t = nums.back();//取nums的最后一个元素
                nums[idx] = t;
                m[t].erase(nums.size()-1);
                m[t].insert(idx);
            }
    
            nums.pop_back();
            return true;
        }
          
        
        /** Get a random element from the set. */
        int getRandom() {
            if (nums.size() == 0) {
                    return NULL;
                }
            int randomIndex = (int) (rand() % nums.size()); // 0 ~ size -1
            return nums[randomIndex];
        }
        
        
    //建立两个数据结构,一个是vector<int>,另一个是unordered_map<int,unordered_set<int>>;  
    private:   
        vector<int> nums;
        unordered_map<int,unordered_set<int>> m;
    };

    4.Leetcode上用时更少的范例

            这个虽然是当前我提交时,看到的运行时间最短的实现方法,但是这个方法严格来讲不是最好的,因为priority_queue<int>的增删的时间复杂度不是O(1)的,而是O(logN)。这里可以参考文末的参考博客,作者Grandyang一开始用的是优先队列(priority_queue),后面在博友的建议,改用了集合unordered_set。

            提速的原因之一在于末尾的“static const auto io_sync_off = []()”的代码,原因可以参考我的另一篇博文Leetcode 295. 数据流的中位数

    class RandomizedCollection
    {
    public:
        RandomizedCollection()
        {
            srand(time(nullptr));
        }
        
        bool insert(int val)
        {
            map[val].push(vOrders.size());
            vOrders.push_back(val);
            return map[val].size() <= 1;
        }
        
        bool remove(int val)
        {
            if ( map[val].empty() )
                return false;
            auto &heap = map[val];
            int i = heap.top();
            heap.pop();
            int val2 = vOrders.back();
            if ( val != val2 )
            {
                vOrders[i] = val2;
                auto &heap2 = map[val2];
                heap2.pop();
                heap2.push(i);
            }
            vOrders.pop_back();
            return true;
        }
        
        int getRandom()
        {
            return vOrders[rand() % vOrders.size()];
        }
    
    protected:
        vector<int> vOrders;
        unordered_map<int, priority_queue<int>> map;    
    };
    
    static const auto io_sync_off = []()
        {
            // turn off sync
            std::ios::sync_with_stdio(false);
            // untie in/out streams
            std::cin.tie(nullptr);
            return nullptr;
        }();

    5.补充说明

            这篇博客更多是学习大牛Grandyang的Leetcode All in One的博客集,绝大部分内容是参考的他的博客,链接附在文末。

            这道题是hard级别的,我个人习惯通过写博客的方式来学习和加深对于问题的理解,用自己能够快速理解的方式呈现出来,方便自己日后的回顾(写一篇一年后能看懂的博客),如果有朋友因博文缺乏足够的原创内容而不满,还请多多谅解。毕竟,每个人都有自己的Style!

    参考资料:

    1.[LeetCode] Insert Delete GetRandom O(1) - Duplicates allowed 常数时间内插入删除和获得随机数 - 允许重复

  • 相关阅读:
    判断上传的文件是否为图片
    P17 更多文件操作
    p16 读写文件
    VMWare Workstation 7.1.2.301548
    Oracle SQL Developer语言设置
    HTC Android 存储卡文件夹
    CentOS 添加EPEL
    Silverlight应用程序的本地通讯
    SQL Server 2008 R2 序列号
    VMware 7 注册码
  • 原文地址:https://www.cnblogs.com/paulprayer/p/9958445.html
Copyright © 2011-2022 走看看