362. Design Hit Counter

1. if we need to keep all the historical data

1. if we need to keep all the historical data
use treemap<time, count>

hit: O(logn)
getHits: O(logn)
class HitCounter {
    
    TreeMap<Integer, Integer> map;
    int count = 0;
    public HitCounter() {
        map = new TreeMap<>();
    }
    
    public void hit(int timestamp) {
        count++;
        map.put(timestamp, count);
    }
    
    public int getHits(int timestamp) {
        Integer cur = map.floorKey(timestamp);
        int curCount = cur != null ? map.get(cur) : 0;
        
        int start = timestamp - 300;
        if (start >= 0) {
            Integer before = map.floorKey(start);
            return curCount - (before != null ? map.get(before) : 0);
        }
        return curCount;
    }
}

/**
 * Your HitCounter object will be instantiated and called as such:
 * HitCounter obj = new HitCounter();
 * obj.hit(timestamp);
 * int param_2 = obj.getHits(timestamp);
 
 the past 300 seconds).
 
 timestamp parameter (in seconds granularity),
 timestamp is monotonically increasing
 
 Several hits may arrive roughly at the same time.
 
 Records a hit that happened at timestamp (in seconds). Several hits may happen at the same timestamp.
 
 Returns the number of hits in the past 5 minutes from timestamp (i.e., the past 300 seconds).
 
 nput
["HitCounter", "hit", "hit", "hit", "getHits", "hit", "getHits", "getHits"]
[[],            [1],    [2],   [3],    [4],     [300], [300],      [301]]  => time
Output
[null, null, null, null, 3, null, 4, 3]

hits depends on time

1. if we need to keep all the historical data
use treemap<time, count>


2. no need to keep the historical data
but!

All the calls are being made to the system in chronological order (i.e., timestamp is monotonically increasing).

this means getHits(time), the time is incresing, we dont need to look up old data

so the idea is queue, FIFO

 */

2. no need to keep the historical data

use queue, FIFO

T:
hit(): O(1)

getHits(): O(m)
ๆœ€ๅฅฝ็š„็‹€ๆณ, ็›ดๆŽฅๆ‹ฟๅˆฐ, ไธ็”จ poll, O(1)
ๆœ€ๅทฎ็š„็‹€ๆณๆ˜ฏ, ๅƒๆ˜ฏๅœจ time = 1ๆ™‚ hitไบ† m ๆฌก, ๅˆฐ 301 ็ง’ ๆ‰ get, ้€™ๆจฃ่ฆ poll() m ๆฌก => O(m)

S: O(n), ๅฆ‚ๆžœๅœจ 300็ง’ๅ…งๆœ‰ n ๆฌก hits
class HitCounter {

    Queue<Integer> queue;
    public HitCounter() {
        queue = new LinkedList<>();
    }
    
    public void hit(int timestamp) {
        queue.offer(timestamp);
    }
    
    public int getHits(int timestamp) {
        // so if not meets the 300 seconds range's data, just abadon it
        while (!queue.isEmpty() && timestamp - queue.peek() >= 300) {
            queue.poll();
        }
        return queue.size();
    }
}

/**
ex:
     hit    getHits
[[], [1],  [301]], so abadon queue's peek

T:
hit(): O(1)

getHits(): O(m)
ๆœ€ๅฅฝ็š„็‹€ๆณ, ็›ดๆŽฅๆ‹ฟๅˆฐ, ไธ็”จ poll, O(1)
ๆœ€ๅทฎ็š„็‹€ๆณๆ˜ฏ, ๅƒๆ˜ฏๅœจ time = 1ๆ™‚ hitไบ† m ๆฌก, ๅˆฐ 301 ็ง’ ๆ‰ get, ้€™ๆจฃ่ฆ poll() m ๆฌก => O(m)

S: O(n), ๅฆ‚ๆžœๅœจ 300็ง’ๅ…งๆœ‰ n ๆฌก hits
 */

but for huge data at the same time, queue is easy out of memory

# circular buffer idea

so just maintain two arrays (size 300) to keep data

T:

hit: O(1)

getHits: O(300)

S: O(600)

class HitCounter {

    int[] times;
    int[] hits;
    public HitCounter() {
        times = new int[300];
        hits = new int[300];
    }
    
    public void hit(int timestamp) {
        int idx = timestamp%300;
        if (times[idx] != timestamp) {
            times[idx] = timestamp;
            hits[idx] = 1;
        } else {
            hits[idx]++;
        }
    }
    
    public int getHits(int timestamp) {
        int result = 0;
        for (int i = 0; i < 300; i++) {
            // 301็ง’getHits,ไธ้œ€่ฆ 1็ง’็š„ data, so 301-i < 300, so i >= 2
            if (timestamp - times[i] < 300) { 
                result += hits[i];
            }
        }
        return result;
    }
}

/**
 * Your HitCounter object will be instantiated and called as such:
 * HitCounter obj = new HitCounter();
 * obj.hit(timestamp);
 * int param_2 = obj.getHits(timestamp);
 */

more comment

```java
class HitCounter {

    int[] hits = new int[300];
    int[] times = new int[300];
    public HitCounter() {
        this.hits = new int[300];
        this.times = new int[300];
    }
    
    public void hit(int timestamp) { 
        int i = timestamp%300;
        if (times[i] != timestamp) { // new timestamp, so override it, and count = 1
            times[i] = timestamp;
            hits[i] = 1;
        } else {
            hits[i]++; // same timestamp, so add count
        }
    }
    
    public int getHits(int timestamp) {
        int result = 0;
        for (int i = 0; i < 300; i++) {
            if (timestamp - times[i] < 300) { // only need past 300 secs
                result += hits[i];
            }
        }
        return result;
    }
}

/**
 * Your HitCounter object will be instantiated and called as such:
 * HitCounter obj = new HitCounter();
 * obj.hit(timestamp);
 * int param_2 = obj.getHits(timestamp);

 hitTime % 300
  |           301
          300
 |----------|         
              
 1 2 3 4. 300 301. hits
                                 1     store in 1
                                 when 
 hit [300] -> store with t%300, 301 -> store in 1 

 this way only is suitable for hit and getHits by time order

This is very similar to the circular buffer I used in my senior design, very common practice I believe


use queue -> o(n)
301 300 4  3  2  1
 */
```

Last updated