【每日算法】數據結構綜合運用:「哈希表套數組」&「哈希表套樹」 |Python 主題月

本文正在參加「Python主題月」,詳情查看 活動連接html

題目描述

這是 LeetCode 上的 981. 基於時間的鍵值存儲 ,難度爲 中等node

Tag : 「設計數據結構」、「哈希表」、「數組」、「紅黑樹」git

建立一個基於時間的鍵值存儲類 TimeMap,它支持下面兩個操做:github

  1. set(string key, string value, int timestamp)
    • 存儲鍵 key、值 value,以及給定的時間戳 timestamp。
  2. get(string key, int timestamp)
    • 返回先前調用 set(key, value, timestamp_prev) 所存儲的值,其中 timestamp_prev <= timestamp。
    • 若是有多個這樣的值,則返回對應最大的  timestamp_prev 的那個值。
    • 若是沒有值,則返回空字符串("")。

示例 1:數組

輸入:inputs = ["TimeMap","set","get","get","set","get","get"], inputs = [[],["foo","bar",1],["foo",1],["foo",3],["foo","bar2",4],["foo",4],["foo",5]]

輸出:[null,null,"bar","bar",null,"bar2","bar2"]

解釋:  
TimeMap kv;   
kv.set("foo", "bar", 1); // 存儲鍵 "foo" 和值 "bar" 以及時間戳 timestamp = 1   
kv.get("foo", 1);  // 輸出 "bar"   
kv.get("foo", 3); // 輸出 "bar" 由於在時間戳 3 和時間戳 2 處沒有對應 "foo" 的值,因此惟一的值位於時間戳 1 處(即 "bar")   
kv.set("foo", "bar2", 4);   
kv.get("foo", 4); // 輸出 "bar2"   
kv.get("foo", 5); // 輸出 "bar2"   
複製代碼

示例 2:markdown

輸入:inputs = ["TimeMap","set","set","get","get","get","get","get"], inputs = [[],["love","high",10],["love","low",20],["love",5],["love",10],["love",15],["love",20],["love",25]]

輸出:[null,null,null,"","high","high","low","low"]
複製代碼

提示:數據結構

  • 全部的鍵/值字符串都是小寫的。
  • 全部的鍵/值字符串長度都在 [1, 100] 範圍內。
  • 全部 TimeMap.set 操做中的時間戳 timestamps 都是嚴格遞增的。
  • 1 <= timestamp <= 1 0 7 10^7
  • TimeMap.set 和 TimeMap.get 函數在每一個測試用例中將(組合)調用總計 120000 次。

哈希表套數組

因爲 timestamp 是嚴格遞增,且沒有刪除 KV 的操做。app

咱們可使用哈希表套數組的方式進行實現,從而達到均攤 O ( 1 ) O(1) 的插入操做和 O ( log n ) O(\log{n}) 的查詢操做。dom

具體的,爲了方便理解,咱們能夠先建一個 Node 類,類中包含鍵值對和時間戳信息。函數

而後使用一個全局哈希表 map 記錄某個 key 對應了哪些 Node。其中多個 Node 是以動態數組的形式進行「以 timestamp 升序」存儲:

  • set 操做:以 O ( 1 ) O(1) 的複雜度找到某個 key 對應的數組,利用 timestamp 嚴格遞增的特性,以 O ( 1 ) O(1) 複雜度將新 Node 加入當前數組尾部;
  • get 操做:以 O ( 1 ) O(1) 的複雜度找到某個 key 對應的數組,利用 timestamp 嚴格遞增的特性,經過二分以 O ( log n ) O(\log{n}) 複雜度找到可能符合條件的 Node

Java 代碼:

class TimeMap {
    class Node {
        String k, v; 
        int t;
        Node (String _k, String _v, int _t) {
            k = _k; v = _v; t = _t;
        }
    }
    
    Map<String, List<Node>> map = new HashMap<>();
    public void set(String k, String v, int t) {
        List<Node> list = map.getOrDefault(k, new ArrayList<>());
        list.add(new Node(k, v, t));
        map.put(k, list);
    }
    
    public String get(String k, int t) {
        List<Node> list = map.getOrDefault(k, new ArrayList<>());
        if (list.isEmpty()) return "";
        int n = list.size();
        int l = 0, r = n - 1;
        while (l < r) {
            int mid = l + r + 1 >> 1;
            if (list.get(mid).t <= t) {
                l = mid;
            } else {
                r = mid - 1;
            }
        }
        return list.get(r).t <= t ? list.get(r).v : "";
    }
}
複製代碼

Python 3 代碼:

class TimeMap:

    def __init__(self):
        """ Initialize your data structure here. """
        self.map = defaultdict(list)

    def set(self, key: str, value: str, timestamp: int) -> None:
        self.map[key].append((key,value,timestamp))

    def get(self, key: str, timestamp: int) -> str:
        if key not in self.map:
            return ""
        lt = self.map[key]
        n = len(lt)
        l, r = 0, n - 1
        while l  < r:
            mid = l + r + 1 >> 1
            if lt[mid][2] <= timestamp:
                l = mid
            else:
                r = mid - 1
        return lt[r][1] if lt[r][2] <= timestamp else ""
複製代碼
  • 時間複雜度:set 操做的複雜度爲 O ( 1 ) O(1) get 操做的複雜度爲 O ( log n ) O(\log{n})
  • 空間複雜度: O ( n ) O(n)

哈希表套樹

若是增長 del 操做呢?咱們須要作出何種調整?

考慮在原題的基礎上,增長一個 String del(String k, int t) 的功能:將嚴格等於鍵和時間戳的 KV 對刪掉。

因爲存在刪除 KV 的動做,咱們須要將實現從「哈希表套數組」改爲「哈希表套樹」,這裏直接使用基於紅黑樹實現的 TreeMap 便可。

同時爲了驗證刪除邏輯的正確性,咱們在 get 動做發生前,先產生一次隨機性的刪除,刪除後又從新插入。

Java 代碼:

class TimeMap {
    class Node {
        String k, v;
        int t;
        Node (String _k, String _v, int _t) {
            k = _k; v = _v; t = _t;
        }
    }

    Map<String, TreeMap<Integer, Node>> map = new HashMap<>();
    public void set(String k, String v, int t) {
        update(k, t);
        TreeMap<Integer, Node> ts = map.getOrDefault(k, new TreeMap<Integer, Node>());
        ts.put(t, new Node(k, v, t));
        map.put(k, ts);
    }

    Node _get(String k, int t) {
        TreeMap<Integer, Node> ts = map.get(k);
        if (ts == null) return null;
        Map.Entry<Integer, Node> entry = ts.floorEntry(t);
        if (entry == null) return null;
        Node node = entry.getValue();
        return node;
    }

    public String get(String k, int t) {
        randomDel();
        Node node = _get(k, t);
        return node != null && node.t <= t ? node.v : "";
    }

    public String del(String k, int t) {
        TreeMap<Integer, Node> ts = map.get(k);
        if (ts == null) return null;
        Map.Entry<Integer, Node> entry = ts.floorEntry(t);
        if (entry == null) return null;
        Node node = entry.getValue();
        if (node != null && node.t == t) {
            ts.remove(t);
            return node.v;
        }
        return "";
    }

    List<String> allInfo = new ArrayList<>(); 
    Random random = new Random();
    // 保存全部的 kt 信息
    void update(String k, int t) {
        String nk = k + "_" + t;
        allInfo.add(nk);
    } 
    // 隨機刪除,再從新插入,驗證代碼正確性
    void randomDel() {
        int idx = random.nextInt(allInfo.size());
        String[] ss = allInfo.get(idx).split("_");
        String k = ss[0];
        int t = Integer.parseInt(ss[1]);
        Node node = _get(k, t);
        del(node.k, node.t);
        set(node.k, node.v, node.t);
    }
}
複製代碼

Python 3 代碼:

from sortedcontainers import SortedDict

class TimeMap:

    def __init__(self):
        """ Initialize your data structure here. """
        self.map = defaultdict(SortedDict)

    def set(self, key: str, value: str, timestamp: int) -> None:
        self.map[key][timestamp] = value

    def get(self, key: str, timestamp: int) -> str:
        if key not in self.map:
            return ""
        keys = self.map[key].keys()
        idx = self.map[key].bisect_left(timestamp)
        if idx == len(keys) or keys[idx] > timestamp:
            idx -= 1
        return self.map[key][keys[idx]] if idx >= 0 else ""
複製代碼
  • 時間複雜度:set 操做的複雜度爲 O ( log n ) O(\log{n}) get 操做會完成隨機刪除/從新插入/查詢的動做,複雜度均爲爲 O ( log n ) O(\log{n}) ,整個 get 的複雜度還是 O ( log n ) O(\log{n}) (只是常數變大了)
  • 空間複雜度: O ( n ) O(n)

最後

這是咱們「刷穿 LeetCode」系列文章的第 No.981 篇,系列開始於 2021/01/01,截止於起始日 LeetCode 上共有 1916 道題目,部分是有鎖題,咱們將先把全部不帶鎖的題目刷完。

在這個系列文章裏面,除了講解解題思路之外,還會盡量給出最爲簡潔的代碼。若是涉及通解還會相應的代碼模板。

爲了方便各位同窗可以電腦上進行調試和提交代碼,我創建了相關的倉庫:github.com/SharingSour…

在倉庫地址裏,你能夠看到系列文章的題解連接、系列文章的相應代碼、LeetCode 原題連接和其餘優選題解。

相關文章
相關標籤/搜索