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)
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。
總結
-
先來說下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的數,表示遞減。 -
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; }