1. 程式人生 > 其它 >ARTS Week 31

ARTS Week 31

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 *將會嚴重影響整體應用程式效能:

  1. 列式資料庫,很多資料庫都是列式資料庫,將不同的列儲存到不同的資料結構中,如此使得SUM(column)更快,而SELECT *卻會比較慢。
  2. 覆蓋查詢。對比 SELECT id, surname FROM user WHERE surname < 'c';SELECT * FROM user WHERE surname < 'c';,如果在id上存在索引,那麼前者可以通過索引來快速讀取到行中對應內容進行比較,而後者卻不行:
  3. 列的數量,當一個表中列太多時,僅通過id依舊無法讀取到所有列中資料
  4. 生成列/虛擬列。SQL語句執行時會計算生成虛擬列,有些情況下會寫入磁碟進行儲存,有些情況下會即使計算,當即使計算時,那麼將會帶來更大的開銷
  5. 檢視。檢視建立在JOIN操作上,只選擇需要的列可能會忽略一些不必要的表,從而使查詢速度更快
  6. InnoDB 文字和 BLOB。在 InnoDB(MariaDB 和 MySQL 預設儲存引擎)中,大的可變長度文字和二進位制列儲存在單獨的記憶體頁中。當長資料儲存在單獨的頁面中,讀取它至少需要一次物理讀取。這會影響效能。

最後,SELECT * 不是一個好的做法,但它對查詢效能和伺服器資源使用的影響通常被誇大了。通常其他方面更為重要,即使用索引和避免使用內部臨時表進行兩步排序。

Tip

最近剛開始接觸 Shell 指令碼,Shell 指令碼中的多行註釋寫法:

: '
This is a
comment
in shell script
'

Share

本週是隔離的第二週,預計接下來的幾周還會繼續被隔離,目前已基本適應了這樣的生活。打算先休息停更一段時間,好好地整理、思考一下,望周知。