寶寶也能看懂的 leetcode 周賽 - 175 - 3

1348. 推文計數

Hi 你們好,我是張小豬。歡迎來到『寶寶也能看懂』系列之 leetcode 周賽題解。node

這裏是第 175 期的第 3 題,也是題目列表中的第 1348 題 -- 『推文計數』git

題目描述

請你實現一個可以支持如下兩種方法的推文計數類 TweetCountsgithub

  1. recordTweet(string tweetName, int time)shell

    • 記錄推文發佈狀況:用戶 tweetName 在 time(以  爲單位)時刻發佈了一條推文。
  2. 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)>,其中 idelta(取決於 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 來匹配時間,因此咱們經過一個字典來進行存儲。而後對於給定的 startend 兩個參數,咱們能夠結合 freq 輕鬆的計算出時間間隔的數量。最後,遍歷計數便可。具體流程以下:

  • 構造函數

    • 申明 freq 的轉換字典以及用於存儲數據的字典。
  • recordTweet

    • 根據 name 去字典裏記錄 time 便可。
  • getTweetCountsPerFrequency

    • 根據 freq 獲取到時間間隔長度。
    • 根據 startend 計算出時間間隔數量。
    • 根據 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 不到這個點。小豬仍是去快樂的恰飯好了,喵喵喵 >.<

相關連接

qrcode_green.jpeg

相關文章
相關標籤/搜索