Welcome to Subscribe On Youtube
Question
Formatted question description: https://leetcode.ca/all/362.html
Design a hit counter which counts the number of hits received in the past 5
minutes (i.e., the past 300
seconds).
Your system should accept a timestamp
parameter (in seconds granularity), and you may assume that calls are being made to the system in chronological order (i.e., timestamp
is monotonically increasing). Several hits may arrive roughly at the same time.
Implement the HitCounter
class:
HitCounter()
Initializes the object of the hit counter system.void hit(int timestamp)
Records a hit that happened attimestamp
(in seconds). Several hits may happen at the sametimestamp
.int getHits(int timestamp)
Returns the number of hits in the past 5 minutes fromtimestamp
(i.e., the past300
seconds).
Example 1:
Input ["HitCounter", "hit", "hit", "hit", "getHits", "hit", "getHits", "getHits"] [[], [1], [2], [3], [4], [300], [300], [301]] Output [null, null, null, null, 3, null, 4, 3] Explanation HitCounter hitCounter = new HitCounter(); hitCounter.hit(1); // hit at timestamp 1. hitCounter.hit(2); // hit at timestamp 2. hitCounter.hit(3); // hit at timestamp 3. hitCounter.getHits(4); // get hits at timestamp 4, return 3. hitCounter.hit(300); // hit at timestamp 300. hitCounter.getHits(300); // get hits at timestamp 300, return 4. hitCounter.getHits(301); // get hits at timestamp 301, return 3.
Constraints:
1 <= timestamp <= 2 * 109
- All the calls are being made to the system in chronological order (i.e.,
timestamp
is monotonically increasing). - At most
300
calls will be made tohit
andgetHits
.
Follow up: What if the number of hits per second could be huge? Does your design scale?
Algorithm
Use a queue
, add the current timestamp to the queue every time you click, and then when you need to get the number of clicks, we start from the beginning of the queue,
- If the time stamp at the beginning is beyond 5 minutes, delete it until the time stamp at the beginning stops within 5 minutes
- Then the number of elements returned to the queue is the requested number of clicks
Follow-up question
Two one-dimensional arrays times[]
and hits[]
with a size of 300 are defined, which are used to store timestamps and hits respectively.
In the click function, take the time stamp to 300, and then check whether the previously saved time stamp in this position is the same as the current time stamp. It also means that it is the same time stamp, then the corresponding number of clicks will increase by 1, if not the same, it means that five minutes have passed, then reset the corresponding number of clicks to 1.
Then when returning the number of hits, we need to traverse the times array to find all the positions within 5 minutes, and then add up the number of clicks in the corresponding positions in hits.
Need further clarify:
- real-time getHits() ?
- batch off-line process, then getHits() ?
1st thought is map-reduce, distribute to multiple hosts, so when getHits() need a broadcast to collect all data
Possible options:
- use a lock
- equal distribution each machine works independently to count its own users from the past minute. When we request the global number, we just need to add all counters together.
Code
-
import java.util.ArrayDeque; public class Design_Hit_Counter { public class HitCounter { private ArrayDeque<Integer> queue; // @note: ArrayDeque has no capacity restrictions // Why is ArrayDeque better than LinkedList? // If you need add/remove of the both ends, ArrayDeque is significantly better than a linked list // https://stackoverflow.com/questions/6163166/why-is-arraydeque-better-than-linkedlist /** Initialize your data structure here. */ public HitCounter() { queue = new ArrayDeque<Integer>(); } /** Record a hit. @param timestamp - The current timestamp (in seconds granularity). */ public void hit(int timestamp) { queue.offer(timestamp); } /** Return the number of hits in the past 5 minutes. @param timestamp - The current timestamp (in seconds granularity). */ // Time Complexity : O(n) public int getHits(int timestamp) { int startTime = timestamp - 300; // remove all hits over 300 seconds old while(!queue.isEmpty() && queue.peek() <= startTime) { queue.poll(); } return queue.size(); } } public class HitCounterFollowUp { private int[] times; private int[] hits; /** Initialize your data structure here. */ public HitCounterFollowUp() { int[] times = new int[300]; int[] hits = new int[300]; } /** Record a hit. @param timestamp - The current timestamp (in seconds granularity). */ public void hit(int timestamp) { int idx = timestamp % 300; if (times[idx] != timestamp) { times[idx] = timestamp; // update with most recent 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). */ // Time Complexity : O(n) public int getHits(int timestamp) { int res = 0; for (int i = 0; i < 300; ++i) { if (timestamp - times[i] < 300) { // values in times[] not ordered at all res += hits[i]; } } return res; } } /** * Your HitCounter object will be instantiated and called as such: * HitCounter obj = new HitCounter(); * obj.hit(timestamp); * int param_2 = obj.getHits(timestamp); */ } ############ class HitCounter { private Map<Integer, Integer> counter; /** Initialize your data structure here. */ public HitCounter() { counter = new HashMap<>(); } /** Record a hit. @param timestamp - The current timestamp (in seconds granularity). */ public void hit(int timestamp) { counter.put(timestamp, counter.getOrDefault(timestamp, 0) + 1); } /** Return the number of hits in the past 5 minutes. @param timestamp - The current timestamp (in seconds granularity). */ public int getHits(int timestamp) { int hits = 0; for (Map.Entry<Integer, Integer> entry : counter.entrySet()) { if (entry.getKey() + 300 > timestamp) { hits += entry.getValue(); } } return hits; } } /** * Your HitCounter object will be instantiated and called as such: * HitCounter obj = new HitCounter(); * obj.hit(timestamp); * int param_2 = obj.getHits(timestamp); */
-
// OJ: https://leetcode.com/problems/design-hit-counter/ // Time: // HitCounter: O(1) // hit, getHits: amortized O(1) // Space: O(N) class HitCounter { queue<int> q; void clear(int timestamp) { int start = timestamp - 300; while (q.size() && q.front() <= start) q.pop(); } public: HitCounter() {} void hit(int timestamp) { clear(timestamp); q.push(timestamp); } int getHits(int timestamp) { clear(timestamp); return q.size(); } };
-
from collections import deque class HitCounter: # queue def __init__(self): """ Initialize your data structure here. """ self.hits = deque() def hit(self, timestamp: int) -> None: """ Record a hit. @param timestamp - The current timestamp (in seconds granularity). """ self.hits.append(timestamp) def getHits(self, timestamp: int) -> int: """ Return the number of hits in the past 5 minutes. @param timestamp - The current timestamp (in seconds granularity). """ while self.hits and self.hits[0] <= timestamp - 300: self.hits.popleft() return len(self.hits) class HitCounter: # follow-up def __init__(self): self.times = [0] * 300 self.hits = [0] * 300 def hit(self, timestamp: int) -> None: idx = timestamp % 300 if self.times[idx] != timestamp: self.times[idx] = timestamp self.hits[idx] = 1 else: self.hits[idx] += 1 def getHits(self, timestamp: int) -> int: res = 0 for i in range(300): if timestamp - self.times[i] < 300: res += self.hits[i] return res # Your HitCounter object will be instantiated and called as such: # obj = HitCounter() # obj.hit(timestamp) # param_2 = obj.getHits(timestamp) ############## class HitCounter: # extra o(N) space def __init__(self): """ Initialize your data structure here. """ self.counter = Counter() def hit(self, timestamp: int) -> None: """ Record a hit. @param timestamp - The current timestamp (in seconds granularity). """ self.counter[timestamp] += 1 def getHits(self, timestamp: int) -> int: """ Return the number of hits in the past 5 minutes. @param timestamp - The current timestamp (in seconds granularity). """ return sum([v for t, v in self.counter.items() if t + 300 > timestamp]) # Your HitCounter object will be instantiated and called as such: # obj = HitCounter() # obj.hit(timestamp) # param_2 = obj.getHits(timestamp)
-
use std::{collections::BinaryHeap, cmp::Reverse}; struct HitCounter { /// A min heap pq: BinaryHeap<Reverse<i32>>, } impl HitCounter { fn new() -> Self { Self { pq: BinaryHeap::new(), } } fn hit(&mut self, timestamp: i32) { self.pq.push(Reverse(timestamp)); } fn get_hits(&mut self, timestamp: i32) -> i32 { while let Some(Reverse(min_elem)) = self.pq.peek() { if *min_elem <= timestamp - 300 { self.pq.pop(); } else { break; } } self.pq.len() as i32 } }
-
type HitCounter struct { ts []int } func Constructor() HitCounter { return HitCounter{} } func (this *HitCounter) Hit(timestamp int) { this.ts = append(this.ts, timestamp) } func (this *HitCounter) GetHits(timestamp int) int { return len(this.ts) - sort.SearchInts(this.ts, timestamp-300+1) } /** * Your HitCounter object will be instantiated and called as such: * obj := Constructor(); * obj.Hit(timestamp); * param_2 := obj.GetHits(timestamp); */
-
class HitCounter { private ts: number[] = []; constructor() {} hit(timestamp: number): void { this.ts.push(timestamp); } getHits(timestamp: number): number { const search = (x: number) => { let [l, r] = [0, this.ts.length]; while (l < r) { const mid = (l + r) >> 1; if (this.ts[mid] >= x) { r = mid; } else { l = mid + 1; } } return l; }; return this.ts.length - search(timestamp - 300 + 1); } } /** * Your HitCounter object will be instantiated and called as such: * var obj = new HitCounter() * obj.hit(timestamp) * var param_2 = obj.getHits(timestamp) */