LC 0362 [M] Design Hit Counter - ALawliet/algorithms GitHub Wiki

Deque Based Solution

Use deque as container. Deque stores the value of the timestamp. hit: append the timestamp to the deque. O(1) get_hit: O(N). During get_hit, pop out all elements from the left of the queue which do not serve any purpose and are stale.Return the length of the deque. How do we find the elenments which are stale? Condition we want to use:* timestamp-t >= 300* Example: timestamp = 301 and t = 1. Valid Range: [1 to 300], [2 to 301], [3,303]. So 301-1 >= 300. Hence 1 should be popped since it doesnt belong to range 2 to 301. Poor scalability of the solution since we store all timestamps.

class HitCounter(object):
    def __init__(self):
        """
        Initialize your data structure here.
        """
        self.counter = deque()

    def hit(self, timestamp):
        """
        Record a hit.
        @param timestamp - The current timestamp (in seconds granularity).
        :type timestamp: int
        :rtype: void
        """
        self.counter.append(timestamp)

    def getHits(self, timestamp):
        """
        Return the number of hits in the past 5 minutes.
        @param timestamp - The current timestamp (in seconds granularity).
        :type timestamp: int
        :rtype: int
        """
        while self.counter and timestamp -self.counter[0] >= 300:
            self.counter.popleft()
        return len(self.counter)

Dictionary based Solution

The above solution doesnt scale since we add an element to the deque for every hit. Use a dictionary instead and prune the dictionary. Atleast in the dictionary solution, same timestamps will not be repeated. But the dictionary can grow unbounded.

class HitCounter(object):
    def __init__(self):
        """
        Initialize your data structure here.
        """
        self.counter = defaultdict(int)

    def get(self, timestamp):
        return self.prune(timestamp)       

    def hit(self, timestamp):
        """
        Record a hit.
        @param timestamp - The current timestamp (in seconds granularity).
        :type timestamp: int
        :rtype: void
        """
        self.counter[timestamp] = self.counter[timestamp] + 1

    def getHits(self, timestamp):
        """
        Return the number of hits in the past 5 minutes.
        @param timestamp - The current timestamp (in seconds granularity).
        :type timestamp: int
        :rtype: int
        """
        cnt = 0
        for k in self.counter.keys():
            if timestamp - k >= 300:
                del self.counter[k]
            else:
                cnt = cnt + self.counter[k] 
        return cnt

Optimized and Scalable Solution

The third solution creates an array of 300 elements. Every element of the array comprises of [frequency, timestamp]. Timestamp 1 maps to index 0. Timestamp 100 maps to index 99. Use modulo mathematics to update it. hit: O(1). get_hit: O(300). This solution will scale perfectly!

class HitCounter(object):
    def __init__(self):
        """
        Initialize your data structure here.
        """
        self.counter = [[0,i+1] for i in range(300)]
        return

    def hit(self, timestamp):
        """
        Record a hit.
        @param timestamp - The current timestamp (in seconds granularity).
        :type timestamp: int
        :rtype: void
        """
        # ts = 301 means (301-1)%300
        idx = int((timestamp - 1)%300)
        if self.counter[idx][1] == timestamp:
            self.counter[idx][0] += 1
        else:
            self.counter[idx][0] = 1            
            self.counter[idx][1] = timestamp            

    def getHits(self, timestamp):
        """
        Return the number of hits in the past 5 minutes.
        @param timestamp - The current timestamp (in seconds granularity).
        :type timestamp: int
        :rtype: int
        """
        cnt = 0
        for x in self.counter:
            c,t = x[0],x[1]
            if timestamp - t < 300:
                cnt += c
        return cnt
class HitCounter:

def __init__(self):
    """
    Initialize your data structure here.
    """
    self.counter = {i:[0, i+1] for i in range(300)}
    

def hit(self, timestamp):
    """
    Record a hit.
    @param timestamp - The current timestamp (in seconds granularity).
    :type timestamp: int
    :rtype: void
    """
    # ts = 301 means (301-1)%300
    idx = int((timestamp - 1)%300)
    if self.counter[idx][1] == timestamp:
        self.counter[idx][0] += 1
    else:
        self.counter[idx][1] = timestamp
        self.counter[idx][0] = 1            
                   

def getHits(self, timestamp):
    """
    Return the number of hits in the past 5 minutes.
    @param timestamp - The current timestamp (in seconds granularity).
    :type timestamp: int
    :rtype: int
    """
    cnt = 0
    for x in self.counter:
        c,t = self.counter[x][0],self.counter[x][1]
        if timestamp - t < 300:
            cnt += c
    return cnt
class HitCounter:

    def __init__(self):
        """
        Initialize your data structure here.
        """
        self.hits = defaultdict(list)

    def hit(self, timestamp: int) -> None:
        """
        Record a hit.
        @param timestamp - The current timestamp (in seconds granularity).
        """
        self.hits[timestamp + 300].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).
        """
        return sum(len(self.hits[i]) for i in self.hits if timestamp < i)