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 常数时间内插入删除和获得随机数 - 允许重复

  • 相关阅读:
    广域网(ppp协议、HDLC协议)
    0120. Triangle (M)
    0589. N-ary Tree Preorder Traversal (E)
    0377. Combination Sum IV (M)
    1074. Number of Submatrices That Sum to Target (H)
    1209. Remove All Adjacent Duplicates in String II (M)
    0509. Fibonacci Number (E)
    0086. Partition List (M)
    0667. Beautiful Arrangement II (M)
    1302. Deepest Leaves Sum (M)
  • 原文地址:https://www.cnblogs.com/paulprayer/p/9958445.html
Copyright © 2011-2022 走看看