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 at timestamp (in seconds). Several hits may happen at the same timestamp.
  • int getHits(int timestamp) Returns the number of hits in the past 5 minutes from timestamp (i.e., the past 300 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 to hit and getHits.

 

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:

  1. use a lock
  2. 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)
     */
    
    

All Problems

All Solutions