LeetCode 981基於時間的鍵值存儲

LeetCode 981基於時間的鍵值存儲

題目

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

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:

輸入: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 <= 10^7
TimeMap.set 和 TimeMap.get 函數在每個測試用例中將(組合)調用總計 120000 次。

解題思路

哈希+二分查找

題意大概就是給一種數據結構裏面存三個類型的數據,分別是String、String和Integer類型,肯定需要用到HashMap這一數據結構,重點是怎麼使用?換句話說,就是到底是<String, String>、還是<String,Integer>、或者<String,Integer>。注意第二個和第三個是不一樣的。

根據題中的get方法,是將第一個String當做key,然後返回值。所以需要一種新型的Pair類型,很可惜的是,java9纔開始支持Pair的類型,所以還得手寫Pair類型。

具體的數據結構爲HashMap<String,Pair>,String爲key,其中Pair是二元組,存放的是timestamp和value。

因爲set操作中的時間戳都是嚴格遞增的,所以二元組Pair中也應該是遞增的,這樣我們可以根據get操作中的key在哈希表中找到對應的二元組列表pairs,然後根據timestamp在pairs中二分查找。我們需要找到的最大不查過timestamp的時間戳,我們可以查找到第一個查過timestamp的二元組下標i,如果i>0說明存在,否則返回空字符串

class TimeMap {
    class Pair implements Comparable<Pair> {
        int timestamp;
        String value;

        public Pair(int timestamp, String value) {
            this.timestamp = timestamp;
            this.value = value;
        }

        public int hashCode() {
            return timestamp + value.hashCode();
        }

        public boolean equals(Object obj) {
            if (obj instanceof Pair) {
                Pair pair2 = (Pair) obj;
                return this.timestamp == pair2.timestamp && this.value.equals(pair2.value);
            }
            return false;
        }

        public int compareTo(Pair pair2) {
            if (this.timestamp != pair2.timestamp) {
                return this.timestamp - pair2.timestamp;
            } else {
                return this.value.compareTo(pair2.value);
            }
        }
    }

    Map<String, List<Pair>> map;

    public TimeMap() {
        map = new HashMap<String, List<Pair>>();
    }
    
    public void set(String key, String value, int timestamp) {
        List<Pair> pairs = map.getOrDefault(key, new ArrayList<Pair>());
        pairs.add(new Pair(timestamp, value));
        map.put(key, pairs);
    }
    
    public String get(String key, int timestamp) {
        List<Pair> pairs = map.getOrDefault(key, new ArrayList<Pair>());
        // 使用一個大於所有 value 的字符串,以確保在 pairs 中含有 timestamp 的情況下也返回大於 timestamp 的位置
        Pair pair = new Pair(timestamp, String.valueOf((char) 127));
        int i = binarySearch(pairs, pair);
        if (i > 0) {
            return pairs.get(i - 1).value;
        }
        return "";
    }

    private int binarySearch(List<Pair> pairs, Pair target) {
        int low = 0, high = pairs.size() - 1;
        if (high < 0 || pairs.get(high).compareTo(target) <= 0) {
            return high + 1;
        }
        while (low < high) {
            int mid = (high - low) / 2 + low;
            Pair pair = pairs.get(mid);
            if (pair.compareTo(target) <= 0) {
                low = mid + 1;
            } else {
                high = mid;
            }
        }
        return low;
    }
}

作者:LeetCode-Solution
鏈接:https://leetcode-cn.com/problems/time-based-key-value-store/solution/ji-yu-shi-jian-de-jian-zhi-cun-chu-by-le-t98o/
來源:力扣(LeetCode)
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

總結

  1. 先來說下Pair這個類的書寫:

    class Pair implements Comparable<Pair> {
            int timestamp;
            String value;
    
            public Pair(int timestamp, String value) {
                this.timestamp = timestamp;
                this.value = value;
            }
    
            public int hashCode() {
                return timestamp + value.hashCode();
            }
    
            public boolean equals(Object obj) {
                if (obj instanceof Pair) {
                    Pair pair2 = (Pair) obj;
                    return this.timestamp == pair2.timestamp && this.value.equals(pair2.value);
                }
                return false;
            }
    		
            public int compareTo(Pair pair2) {
                if (this.timestamp != pair2.timestamp) {
                    return this.timestamp - pair2.timestamp;
                } else {
                    return this.value.compareTo(pair2.value);
                }
            }
    }
    

    首先是hashCode和equals覆蓋方法,這裏的hashCode方法計算採用相加的方法,hashCode主要是保證不一樣就行,因爲timestamp肯定是不同的,所以可以直接相加即可。其次是equals方法,首先判斷obj是不是這個類的實例,如果是的話,再判斷屬性值是否相等。

    然後是實現Comparable的compareTo的接口方法:首先判斷timestamp是否相等,如果相等的話,再根據value排序。一句話講就是首先根據timestamp排序,如果有一樣的,再根據value排序。返回的數如果爲大於0的數,表示遞減。

  2. set方法不用贅述,因爲都是單純的add操作。看get和二分查找的代碼註釋吧。

    public String get(String key, int timestamp) {
            List<Pair> pairs = map.getOrDefault(key, new ArrayList<Pair>());
            // 使用一個大於所有 value 的字符串,以確保在 pairs 中含有 timestamp 的情況下也返回大於 timestamp 的位置
            Pair pair = new Pair(timestamp, String.valueOf((char) 127));
            int i = binarySearch(pairs, pair);
            if (i > 0) {
                return pairs.get(i - 1).value;
            }
            return "";
        }
    
        private int binarySearch(List<Pair> pairs, Pair target) {
            // 相當於在list中查找一個數
            int low = 0, high = pairs.size() - 1;
            // 如果high小於0,表示只有一個元素或者只有兩個元素的情況
            if (high < 0 || pairs.get(high).compareTo(target) <= 0) {
                return high + 1;
            }
            while (low < high) {
                //二分法的變形
                int mid = (high - low) / 2 + low;
                Pair pair = pairs.get(mid);
                //如果pair<targe的話,將mid設置在mid+1;
                if (pair.compareTo(target) <= 0) {
                    low = mid + 1;
                } else {
                    high = mid;
                }
            }
            return low;
        }
    
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章