ARTS Week 31
阿新 • • 發佈:2022-03-27
Algorithm
本週的 LeetCode 題目為 380. O(1) 時間插入、刪除和獲取隨機元素
實現RandomizedSet
類:
-
RandomizedSet()
初始化RandomizedSet
物件 -
bool insert(int val)
當元素val
不存在時,向集合中插入該項,並返回true
;否則,返回false
。 -
bool remove(int val)
當元素val
存在時,從集合中移除該項,並返回true
;否則,返回false
。 -
int getRandom()
隨機返回現有集合中的一項(測試用例保證呼叫此方法時集合中至少存在一個元素)。每個元素應該有 相同的概率
你必須實現類的所有函式,並滿足每個函式的 平均 時間複雜度為 O(1) 。
為了要讓平均時間複雜保持在 O(1),那麼則需要使用雜湊表來儲存值和索引的對映關係。同時使用一個數組作為實際儲存的資料的資料結構,同時有一個隨機數類以便於生成隨機數,下面具體介紹各個方法的實現方法:
- 初始化方法
RandomizedSet()
:分別初始化上面提到的陣列、雜湊表和隨機數類 - 插入方法
bool insert(int val)
:先判斷val
是否已經存在,若存在則返回false
,若不存在,將val
插入到陣列尾部,並同時更新雜湊表map.put(val, list.size()-1);
,最後返回true
- 刪除方法
bool remove(int val)
:先判斷val
是否已經存在,若不存在返回false
,若存在,先通過雜湊表獲取val
在陣列中索引index,因為要求時間複雜度為O(1),那麼不能使用陣列的刪除方法,而是將陣列索引為index位置的值改為陣列最後一個元素的值list.set(index, lastVal);
,並同時更新雜湊表中的對映關係map.put(lastVal, index);
,之後再移除陣列的最後一個元素(此操作為O(1)複雜度)和雜湊表中關於val
的對映,最後返回true
- 隨機返回一項
int getRandom()
:在陣列索引範圍內使用隨機類隨機生成一個數,返回該下標對應的數即可。
輸入
["RandomizedSet", "insert", "remove", "insert", "getRandom", "remove", "insert", "getRandom"]
[[], [1], [2], [2], [], [1], [2], []]
輸出
[null, true, false, true, 2, true, false, 2]
解釋
RandomizedSet randomizedSet = new RandomizedSet();
randomizedSet.insert(1); // 向集合中插入 1 。返回 true 表示 1 被成功地插入。
randomizedSet.remove(2); // 返回 false ,表示集合中不存在 2 。
randomizedSet.insert(2); // 向集合中插入 2 。返回 true 。集合現在包含 [1,2] 。
randomizedSet.getRandom(); // getRandom 應隨機返回 1 或 2 。
randomizedSet.remove(1); // 從集合中移除 1 ,返回 true 。集合現在包含 [2] 。
randomizedSet.insert(2); // 2 已在集合中,所以返回 false 。
randomizedSet.getRandom(); // 由於 2 是集合中唯一的數字,getRandom 總是返回 2 。
class RandomizedSet {
private List<Integer> list;
private Map<Integer, Integer> map;
Random rand;
public RandomizedSet() {
list = new ArrayList<>();
map = new HashMap<>();
rand = new Random();
}
public boolean insert(int val) {
if (map.containsKey(val) == true) {
return false;
} else {
list.add(val);
map.put(val, list.size()-1);
return true;
}
}
public boolean remove(int val) {
if (map.containsKey(val) == false) {
return false;
} else {
int index = map.get(val);
int lastVal = list.get(list.size()-1);
list.set(index, lastVal);
map.put(lastVal, index);
list.remove(list.size()-1);
map.remove(val);
return true;
}
}
public int getRandom() {
return list.get(rand.nextInt(list.size()));
}
}
/**
* Your RandomizedSet object will be instantiated and called as such:
* RandomizedSet obj = new RandomizedSet();
* boolean param_1 = obj.insert(val);
* boolean param_2 = obj.remove(val);
* int param_3 = obj.getRandom();
*/
Review
本週 Review 的英文文章為:SELECT *
有多慢
最廣為人知的查詢優化規則就是儘可能避免使用SELECT *
,即使需要用到所有列,那麼也應該全部列出它們的名稱。表面看起來SELECT *
會讀取不必要的列,但更深層次的原因是:
- 這些列是從記憶體或磁碟中讀取的,讀取不必要的列會帶來更高的資源使用率和更高的延遲
- 如果是從磁碟中讀取,它可能會被快取,那麼將會浪費不必要的記憶體
- 列也有可能通過網路傳送,將會給伺服器和網路帶來更多開銷
在某些情況下SELECT *
將會嚴重影響整體應用程式效能:
- 列式資料庫,很多資料庫都是列式資料庫,將不同的列儲存到不同的資料結構中,如此使得
SUM(column)
更快,而SELECT *
卻會比較慢。 - 覆蓋查詢。對比
SELECT id, surname FROM user WHERE surname < 'c';
和SELECT * FROM user WHERE surname < 'c';
,如果在id
上存在索引,那麼前者可以通過索引來快速讀取到行中對應內容進行比較,而後者卻不行: - 列的數量,當一個表中列太多時,僅通過id依舊無法讀取到所有列中資料
- 生成列/虛擬列。SQL語句執行時會計算生成虛擬列,有些情況下會寫入磁碟進行儲存,有些情況下會即使計算,當即使計算時,那麼將會帶來更大的開銷
- 檢視。檢視建立在JOIN操作上,只選擇需要的列可能會忽略一些不必要的表,從而使查詢速度更快
- InnoDB 文字和 BLOB。在 InnoDB(MariaDB 和 MySQL 預設儲存引擎)中,大的可變長度文字和二進位制列儲存在單獨的記憶體頁中。當長資料儲存在單獨的頁面中,讀取它至少需要一次物理讀取。這會影響效能。
最後,SELECT *
不是一個好的做法,但它對查詢效能和伺服器資源使用的影響通常被誇大了。通常其他方面更為重要,即使用索引和避免使用內部臨時表進行兩步排序。
Tip
最近剛開始接觸 Shell 指令碼,Shell 指令碼中的多行註釋寫法:
: '
This is a
comment
in shell script
'
Share
本週是隔離的第二週,預計接下來的幾周還會繼續被隔離,目前已基本適應了這樣的生活。打算先休息停更一段時間,好好地整理、思考一下,望周知。