Hi 你們好,我是張小豬。歡迎來到『寶寶也能看懂』系列之 leetcode 周賽題解。node
這裏是第 175 期的第 3 題,也是題目列表中的第 1348 題 -- 『推文計數』git
請你實現一個可以支持如下兩種方法的推文計數類 TweetCounts
:github
recordTweet(string tweetName, int time)
shell
tweetName
在 time
(以 秒 爲單位)時刻發佈了一條推文。getTweetCountsPerFrequency(string freq, string tweetName, int startTime, int endTime)
segmentfault
startTime
(以 秒 爲單位)到結束時間 endTime
(以 秒 爲單位)內,每 分 minute,時 hour 或者 日 day (取決於 freq
)內指定用戶 tweetName
發佈的推文總數。freq
的值始終爲 分 minute,時 hour 或者 日 day 之一,表示獲取指定用戶 tweetName
發佈推文次數的時間間隔。startTime
開始,所以時間間隔爲 [startTime, startTime + delta*1>, [startTime + delta*1, startTime + delta*2>, [startTime + delta*2, startTime + delta*3>, ... , [startTime + delta*i, min(startTime + delta*(i+1), endTime + 1)>
,其中 i
和 delta
(取決於 freq
)都是非負整數。示例:數組
輸入: ["TweetCounts","recordTweet","recordTweet","recordTweet","getTweetCountsPerFrequency","getTweetCountsPerFrequency","recordTweet","getTweetCountsPerFrequency"] [[],["tweet3",0],["tweet3",60],["tweet3",10],["minute","tweet3",0,59],["minute","tweet3",0,60],["tweet3",120],["hour","tweet3",0,210]] 輸出: [null,null,null,null,[2],[2,1],null,[4]] 解釋: TweetCounts tweetCounts = new TweetCounts(); tweetCounts.recordTweet("tweet3", 0); tweetCounts.recordTweet("tweet3", 60); tweetCounts.recordTweet("tweet3", 10); // "tweet3" 發佈推文的時間分別是 0, 10 和 60 。 tweetCounts.getTweetCountsPerFrequency("minute", "tweet3", 0, 59); // 返回 [2]。統計頻率是每分鐘(60 秒),所以只有一個有效時間間隔 [0,60> - > 2 條推文。 tweetCounts.getTweetCountsPerFrequency("minute", "tweet3", 0, 60); // 返回 [2,1]。統計頻率是每分鐘(60 秒),所以有兩個有效時間間隔 1) [0,60> - > 2 條推文,和 2) [60,61> - > 1 條推文。 tweetCounts.recordTweet("tweet3", 120); // "tweet3" 發佈推文的時間分別是 0, 10, 60 和 120 。 tweetCounts.getTweetCountsPerFrequency("hour", "tweet3", 0, 210); // 返回 [4]。統計頻率是每小時(3600 秒),所以只有一個有效時間間隔 [0,211> - > 4 條推文。
提示:數據結構
recordTweet
和 getTweetCountsPerFrequency
,最多有 10000
次操做。0 <= time, startTime, endTime <= 10^9
0 <= endTime - startTime <= 10^4
MEDIUM函數
題目的描述和示例看起來都挺長的,不過主要都是爲了解釋清楚那個時間間隔的計算邏輯。其中須要注意的是,每個時間間隔的區間,是左閉右開的區間,例如 [startTime, startTime + delta)
。若是題目描述中看着不是很明白的話,能夠結合示例中的數據和結果,相信就很容易弄明白這裏面的邏輯啦。性能
不過說實話,小豬看完這道題以後是懵逼的。由於小豬沒看出其中有什麼玄機,彷佛按照要求直接實現就完事了。但是這也是周賽第三題了吧,怎麼也該給它一點面子鴨...hmmmm...裝模做樣的思考了一下,算了,淦就完事了!優化
因爲須要根據 tweetName
來匹配時間,因此咱們經過一個字典來進行存儲。而後對於給定的 start
和 end
兩個參數,咱們能夠結合 freq
輕鬆的計算出時間間隔的數量。最後,遍歷計數便可。具體流程以下:
構造函數
freq
的轉換字典以及用於存儲數據的字典。recordTweet
name
去字典裏記錄 time
便可。getTweetCountsPerFrequency
freq
獲取到時間間隔長度。start
和 end
計算出時間間隔數量。name
獲取時間列表並進行遍歷和計數。基於這個流程,咱們能夠實現相似下面的代碼:
class TweetCounts { constructor() { this.freqInterval = { minute: 60, hour: 3600, day: 86400, }; this.data = new Map(); } recordTweet(name, time) { if (this.data.has(name) === false) { this.data.set(name, []); } this.data.get(name).push(time); } getTweetCountsPerFrequency(freq, name, start, end) { const interval = this.freqInterval[freq]; const ret = new Uint16Array(Math.ceil((end - start + 1) / interval)); if (this.data.has(name)) { for (const time of this.data.get(name)) { if (time > end || time < start) continue; ++ret[Math.floor((time - start + 1) / interval)]; } } return ret; } };
上面的代碼中,因爲咱們存儲的時間是無序的,因此每次 getTweetCountsPerFrequency
方法都會把對應那個 name
的時間列表遍歷一遍,須要消耗 O(n) 的代價。這裏咱們能夠經過二叉樹把它優化到 O(logn) 的時間,不過相應的,每次增長一個 time
的時間會從 O(1) 也變成 O(logn)。
具體的方式就是咱們不用數組來存儲時間,而是替換爲一個二叉搜索樹。流程其實仍是同樣的,因此這裏就直接給代碼了:
const createNode = val => ({ val, left: null, right: null }); class BinarySearchTree { constructor() { this.root = null; } insert(val, cur = this.root) { const node = createNode(val); if (!this.root) { this.root = node; return; } if (val >= cur.val) { !cur.right ? (cur.right = node) : this.insert(val, cur.right); } else { !cur.left ? (cur.left = node) : this.insert(val, cur.left); } } traversal(low, high, interval, intervals, cur = this.root) { if (!cur) return; if (cur.val <= high && cur.val >= low) { ++intervals[Math.floor((cur.val - low + 1) / interval)]; } cur.val > low && this.traversal(low, high, interval, intervals, cur.left); cur.val < high && this.traversal(low, high, interval, intervals, cur.right); } }; class TweetCounts { constructor() { this.freqInterval = { minute: 60, hour: 3600, day: 86400, }; this.data = new Map(); } recordTweet(name, time) { if (this.data.has(name) === false) { this.data.set(name, new BinarySearchTree()); } this.data.get(name).insert(time); } getTweetCountsPerFrequency(freq, name, start, end) { const interval = this.freqInterval[freq]; const ret = new Uint16Array(Math.ceil((end - start + 1) / interval)); this.data.has(name) && this.data.get(name).traversal(start, end, interval, ret); return ret; } };
這段代碼以 192ms 暫時 beats 100%。不過很顯然,能夠繼續優化,由於我這裏只是用了很是簡單的二叉搜索樹,並不能保證樹的深度是 logn,能夠輕鬆的構建出一些用例讓它退化爲 O(n) 性能。因此,咱們能夠把它替換爲一些自平衡二叉搜索樹,以保證樹的深度。
可是,JS 裏缺乏太多內置的數據結構了,因此我這裏就先不寫啦,等到咱們數據結構專題那邊再作相關的實現吧。哼,纔不是由於小豬懶呢(確信臉
說實話這道題確實讓小豬比較懵逼。寫這篇文章的時候看了一眼經過率,只有 20%+。但是小豬卻一直不太明白這道題有什麼玄機,也不明白爲何經過率這麼低,更不明白這道題的點究竟在哪裏。
hmmmm...必定是小豬太苯了,get 不到這個點。小豬仍是去快樂的恰飯好了,喵喵喵 >.<