[LeetCode] Insert Delete GetRandom O(1)
Design a data structure that supports all following operations in average O(1) time.
Note: Duplicate elements are allowed.insert(val)
: Inserts an item val to the collection.remove(val)
: Removes an item val from the collection if present.getRandom
: Returns a random element from current collection of elements. The probability of each element being returned is linearly related to the number of same value the collection contains.
Example:
// Init an empty collection. RandomizedCollection collection = new RandomizedCollection(); // Inserts 1 to the collection. Returns true as the collection did not contain 1. collection.insert(1); // Inserts another 1 to the collection. Returns false as the collection contained 1. Collection now contains [1,1]. collection.insert(1); // Inserts 2 to the collection, returns true. Collection now contains [1,1,2]. collection.insert(2); // getRandom should return 1 with the probability 2/3, and returns 2 with the probability 1/3. collection.getRandom(); // Removes 1 from the collection, returns true. Collection now contains [1,2]. collection.remove(1); // getRandom should return 1 and 2 both equally likely. collection.getRandom();
這題是之前那道Insert Delete GetRandom O(1)的拓展,與其不同的是,之前那道題不能有重複數字,而這道題可以有,那麼就不能像之前那道題那樣建立每個數字和其座標的映射了,但是我們可以建立數字和其所有出現位置的集合之間的對映,雖然寫法略有不同,但是思路和之前那題完全一樣,都是將陣列最後一個位置的元素和要刪除的元素交換位置,然後刪掉最後一個位置上的元素。對於insert函式,我們將要插入的數字在nums中的位置加入m[val]陣列的末尾,然後在陣列nums末尾加入val,我們判斷是否有重複只要看m[val]陣列只有剛加的val一個值還是有多個值。remove函式是這題的難點,我們首先看雜湊表中有沒有val,沒有的話直接返回false。然後我們取出nums的尾元素,把尾元素雜湊表中的位置陣列中的最後一個位置更新為m[val]的尾元素,這樣我們就可以刪掉m[val]的尾元素了,如果m[val]只有一個元素,那麼我們把這個對映直接刪除。然後我們將nums陣列中的尾元素刪除,並把尾元素賦給val所在的位置,注意我們在建立雜湊表的對映的時候需要用堆而不是普通的vector陣列,因為我們每次remove操作後都會移除nums陣列的尾元素,如果我們用vector來儲存數字的座標,而且只移出末尾數字的話,有可能出現前面的座標大小超過了此時nums的大小的情況,就會出錯,所以我們用優先佇列對所有的相同數字的座標進行自動排序,每次把最大位置的座標移出即可,參見程式碼如下:
解法一:
class RandomizedCollection { public: /** Initialize your data structure here. */ RandomizedCollection() {} /** Inserts a value to the collection. Returns true if the collection did not already contain the specified element. */ bool insert(int val) { m[val].push(nums.size()); nums.push_back(val); return m[val].size() == 1; } /** Removes a value from the collection. Returns true if the collection contained the specified element. */ bool remove(int val) { if (m[val].empty()) return false; int idx = m[val].top(); m[val].pop(); if (nums.size() - 1 != idx) { int t = nums.back(); nums[idx] = t; m[t].pop(); m[t].push(idx); } nums.pop_back(); return true; } /** Get a random element from the collection. */ int getRandom() { return nums[rand() % nums.size()]; } private: vector<int> nums; unordered_map<int, priority_queue<int>> m; };
有網友指出上面的方法其實不是真正的O(1)時間複雜度,因為優先佇列的push不是常數級的,博主一看果然是這樣的,為了嚴格的遵守O(1)的時間複雜度,我們將優先佇列換成unordered_set,其插入刪除的操作都是常數量級的,其他部分基本不用變,參見程式碼如下:
解法二:
class RandomizedCollection { public: /** Initialize your data structure here. */ RandomizedCollection() {} /** Inserts a value to the collection. Returns true if the collection did not already contain the specified element. */ bool insert(int val) { m[val].insert(nums.size()); nums.push_back(val); return m[val].size() == 1; } /** Removes a value from the collection. Returns true if the collection contained the specified element. */ bool remove(int val) { if (m[val].empty()) return false; int idx = *m[val].begin(); m[val].erase(idx); if (nums.size() - 1 != idx) { int t = nums.back(); nums[idx] = t; m[t].erase(nums.size() - 1); m[t].insert(idx); } nums.pop_back(); return true; } /** Get a random element from the collection. */ int getRandom() { return nums[rand() % nums.size()]; } private: vector<int> nums; unordered_map<int, unordered_set<int>> m; };
類似題目:
參考資料: