Design a hit counter which counts the number of hits received in the past 5 minutes.
Each function accepts a timestamp parameter (in seconds granularity) and you may assume that calls are being made to the system in chronological order (ie, the timestamp is monotonically increasing). You may assume that the earliest timestamp starts at 1.
It is possible that several hits arrive roughly at the same time.
Example:
HitCounter counter = new HitCounter(); // hit at timestamp 1. counter.hit(1); // hit at timestamp 2. counter.hit(2); // hit at timestamp 3. counter.hit(3); // get hits at timestamp 4, should return 3. counter.getHits(4); // hit at timestamp 300. counter.hit(300); // get hits at timestamp 300, should return 4. counter.getHits(300); // get hits at timestamp 301, should return 3. counter.getHits(301);
Follow up:
What if the number of hits per second could be very large? Does your design scale?
Credits:
Special thanks to @elmirap for adding this problem and creating all test cases.
這道題讓我們設計一個點擊計數器,能夠返回五分鍾內的點擊數,提示了有可能同一時間內有多次點擊。由於操作都是按時間順序的,下一次的時間戳都會大於等於本次的時間戳,那么最直接的方法就是用一個隊列queue,每次點擊時都將當前時間戳加入queue中,然后在需要獲取點擊數時,我們從隊列開頭開始看,如果開頭的時間戳在5分鍾以外了,就刪掉,直到開頭的時間戳在5分鍾以內停止,然后返回queue的元素個數即為所求的點擊數,參見代碼如下:
解法一:
class HitCounter { public: /** Initialize your data structure here. */ HitCounter() {} /** Record a hit. @param timestamp - The current timestamp (in seconds granularity). */ void hit(int timestamp) { q.push(timestamp); } /** Return the number of hits in the past 5 minutes. @param timestamp - The current timestamp (in seconds granularity). */ int getHits(int timestamp) { while (!q.empty() && timestamp - q.front() >= 300) { q.pop(); } return q.size(); } private: queue<int> q; };
下面這種方法和上面的方法很像,用了一個數組保存所有的時間戳,然后要返回點擊數時,只需要從開頭找到第一個在5分鍾的時間戳的坐標,然后用數組總長度減去這個坐標即可,和上面的方法不同的是,這個方法不刪掉之前的時間戳,缺點是會很占空間,而且越到后面效率越低,參見代碼如下:
解法二:
class HitCounter { public: /** Initialize your data structure here. */ HitCounter() {} /** Record a hit. @param timestamp - The current timestamp (in seconds granularity). */ void hit(int timestamp) { v.push_back(timestamp); } /** Return the number of hits in the past 5 minutes. @param timestamp - The current timestamp (in seconds granularity). */ int getHits(int timestamp) { int i, j; for (i = 0; i < v.size(); ++i) { if (v[i] > timestamp - 300) { break; } } return v.size() - i; } private: vector<int> v; };
由於Follow up中說每秒中會有很多點擊,下面這種方法就比較巧妙了,定義了兩個大小為300的一維數組times和hits,分別用來保存時間戳和點擊數,在點擊函數中,將時間戳對300取余,然后看此位置中之前保存的時間戳和當前的時間戳是否一樣,一樣說明是同一個時間戳,那么對應的點擊數自增1,如果不一樣,說明已經過了五分鍾了,那么將對應的點擊數重置為1。那么在返回點擊數時,我們需要遍歷times數組,找出所有在5分中內的位置,然后把hits中對應位置的點擊數都加起來即可,參見代碼如下:
解法三:
class HitCounter { public: /** Initialize your data structure here. */ HitCounter() { times.resize(300); hits.resize(300); } /** Record a hit. @param timestamp - The current timestamp (in seconds granularity). */ void hit(int timestamp) { int idx = timestamp % 300; if (times[idx] != timestamp) { times[idx] = timestamp; hits[idx] = 1; } else { ++hits[idx]; } } /** Return the number of hits in the past 5 minutes. @param timestamp - The current timestamp (in seconds granularity). */ int getHits(int timestamp) { int res = 0; for (int i = 0; i < 300; ++i) { if (timestamp - times[i] < 300) { res += hits[i]; } } return res; } private: vector<int> times, hits; };
類似題目:
參考資料:
https://leetcode.com/problemset/algorithms/
https://leetcode.com/discuss/109492/java-solution-easy-to-understand
https://leetcode.com/discuss/109489/simple-java-solution-with-explanation
https://leetcode.com/discuss/109499/super-easy-design-hit-gethits-fancy-data-structure-is-needed