Leetcode 380. 常數時間插入、刪除和獲取隨機元素
阿新 • • 發佈:2018-11-08
限定 速度 private hash list true keymap 插入 new 等概率隨機返回集合中的一個元素。
題目描述:
設計一個支持在平均 時間復雜度 O(1) 下,執行以下操作的數據結構。
insert(val)
:當元素 val 不存在時,向集合中插入該項。remove(val)
:元素 val 存在時,從集合中移除該項。getRandom
:隨機返回現有集合中的一項。每個元素應該有相同的概率被返回
示例:
// 初始化一個空的集合。 RandomizedSet randomSet = new RandomizedSet(); // 向集合中插入 1 。返回 true 表示 1 被成功地插入。 randomSet.insert(1); // 返回 false ,表示集合中不存在 2 。 randomSet.remove(2); // 向集合中插入 2 。返回 true 。集合現在包含 [1,2] 。 randomSet.insert(2); // getRandom 應隨機返回 1 或 2 。 randomSet.getRandom(); // 從集合中移除 1 ,返回 true 。集合現在包含 [2] 。 randomSet.remove(1); // 2 已在集合中,所以返回 false 。 randomSet.insert(2); // 由於 2 是集合中唯一的數字,getRandom 總是返回 2 。 randomSet.getRandom();
解題思路:
分析:題目的難點在於有delete操作的情況下,要保證getRandom( )
一般地,題目的對時間復雜度的要求越高,都需要使用更多的輔助結構,以“空間換時間”。這裏可以采用“兩個哈希表”(多一個哈希表)或者“一個哈希表加一個數組”(多一個數組)。
漸進思路:
(1)沒有delete(val),只有insert(val)和getRandom( )操作的情況下,連續的插入元素鍵值對<key,index>,因為index在邏輯上是連續,因此getRandom()等概率隨機返回集合中的一個元素很容易實現,rand() % index (0~index-1)即可;
(2)有delete(val)操作,可以刪除元素鍵值對之後,使得index不連續,中間有空洞,所以此時getRandom()產生的index可能正好是被刪除的,導致時間復雜度超過O(1),所以delete(val)操作需要有一些限定條件,即保證每刪除一個元素鍵值對之後,index個數減一,但是整體index在邏輯上是連續的。
例如:0~5 ——> 0~4 ——> 0~3
代碼裏關鍵部分有註釋。
class RandomizedSet { public: /** Initialize your data structure here. */ //建立兩個hash表,一個是<key,index>,另一個是<index,key>; RandomizedSet() { } /** Inserts a value to the set. Returns true if the set did not already contain the specified element. */ bool insert(int val) { //元素不存在時,插入 if(keyIndexMap.count(val) == 0) { keyIndexMap[val] = size; indexKeyMap[size] = val; ++size; return true; } return false; } /** Removes a value from the set. Returns true if the set contained the specified element. */ bool remove(int val) { ////每刪除一個鍵值對,用最後的鍵值對填充該空位,保證整個index在邏輯上連續,size減一 if(keyIndexMap.count(val) == 1) { int removeIndex = keyIndexMap[val]; int lastIndex = --size;//若size=1000,表示0~999;這裏是取最後一個index int lastKey = indexKeyMap[lastIndex]; keyIndexMap[lastKey] = removeIndex; indexKeyMap[removeIndex] = lastKey; keyIndexMap.erase(val); indexKeyMap.erase(lastIndex);//下標方式取val對應的值index return true; } return false; } /** Get a random element from the set. */ int getRandom() { if (size == 0) { return NULL; } //srand((unsigned)time(NULL)); //去掉srand(),保證穩定的產生隨機序列 int randomIndex = (int) (rand() % size); // 0 ~ size -1 return indexKeyMap[randomIndex]; } private: map<int,int> keyIndexMap; map<int,int> indexKeyMap; int size = 0; }; /** * Your RandomizedSet object will be instantiated and called as such: * RandomizedSet obj = new RandomizedSet(); * bool param_1 = obj.insert(val); * bool param_2 = obj.remove(val); * int param_3 = obj.getRandom(); */
用時更少的範例:
這是Leetcode官網上C++完成此題提高的用時排名靠前的代碼,這裏與上面的解法差異就在於額外的輔助結構的選擇,這裏選的是在哈希表的基礎上多增加一個數組,數組操作的時間復雜度和哈希表操作的時間復雜度均為O(1),但是數組時間復雜度O(1)的常數項更小,因此,這種解法效率更高。
class RandomizedSet { public: /** Initialize your data structure here. */ RandomizedSet() { } /** Inserts a value to the set. Returns true if the set did not already contain the specified element. */ bool insert(int val) { if (m.count(val)) return false; nums.push_back(val); m[val] = nums.size() - 1; return true; } /** Removes a value from the set. Returns true if the set contained the specified element. */ bool remove(int val) { if (!m.count(val)) return false; int last = nums.back(); m[last] = m[val]; nums[m[val]] = last; nums.pop_back(); m.erase(val); return true; } /** Get a random element from the set. */ int getRandom() { return nums[rand() % nums.size()]; } //private: //註釋掉private,提高一點速度 vector<int> nums; unordered_map<int, int> m; }; /** * Your RandomizedSet object will be instantiated and called as such: * RandomizedSet obj = new RandomizedSet(); * bool param_1 = obj.insert(val); * bool param_2 = obj.remove(val); * int param_3 = obj.getRandom(); */
Leetcode 380. 常數時間插入、刪除和獲取隨機元素