Skip to content

Commit d223042

Browse files
committed
solve Number of Recent Calls
1 parent bcb6a62 commit d223042

File tree

2 files changed

+117
-0
lines changed

2 files changed

+117
-0
lines changed

problems/number_of_recent_calls.py

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
from collections import deque
2+
3+
4+
class RecentCounter:
5+
"""
6+
Your RecentCounter object will be instantiated and called as such:
7+
obj = RecentCounter()
8+
param_1 = obj.ping(t)
9+
10+
Example:
11+
Input
12+
["RecentCounter", "ping", "ping", "ping", "ping"]
13+
[[], [1], [100], [3001], [3002]]
14+
Output
15+
[null, 1, 2, 3, 3]
16+
"""
17+
18+
def __init__(self):
19+
self.ping_ts = []
20+
self.length = 0
21+
22+
def ping(self, t: int) -> int:
23+
# At any instant of time, self.ping_ts has maximum length
24+
# of 3001. So time complexity is O(1) and space complexity
25+
# is O(1).
26+
self.ping_ts.append(t)
27+
28+
i, count = self.length, 0
29+
while i >= 0 and self.ping_ts[i] >= t - 3000:
30+
count, i = count + 1, i - 1
31+
32+
self.ping_ts, self.length = self.ping_ts[i + 1 :], count
33+
34+
return count
35+
36+
37+
class RecentCounterOfficial:
38+
"""
39+
== Overview ==
40+
We are given a sequence of ping calls, i.e. [t1, t2, ... tn], ordered by the
41+
chronological order of their arrival time.
42+
Given the current ping call ti, we are asked to count the number of previous calls
43+
that fall in the range [ti - 3000, ti].
44+
45+
== Approach 1: Iteration over Sliding Window ==
46+
The idea is that we can use a container such as array or list to keep track of all
47+
the incoming ping calls. At each occasion of ping(t) call, first we append the call
48+
to the container, and then starting from the current call, we iterate backwards to
49+
count the calls that fall into the time range of [t-3000, t].
50+
51+
Once the ping calls become outdated, i.e. out of the scope of [t - 3000, t], we do
52+
not need to keep them any longer in the container, since they will not contribute
53+
to the solution later.
54+
As a result, one optimization that we could do is that rather than keeping all the
55+
historical ping calls in the container, we could remove the outdated calls on the
56+
go, which can avoid overflow of the container and reduce the memory consumption to
57+
the least.
58+
59+
In summary, out container will function like a sliding window over the ever-
60+
growing sequence of ping calls.
61+
62+
== Algorithm ==
63+
To implement the sliding window, we could use deque in Python.
64+
Then the ping(t) function can be implemented in two steps:
65+
- Step 1: We append the current ping call to the tail of the sliding window.
66+
- Step 2: Starting from the head of the sliding window, we remove the outdated
67+
calls, until we come across a still valid ping call.
68+
As a result, the remaining calls in the sliding window are the ones that fall into
69+
the range [t-3000,t].
70+
71+
== Complexity Analysis ==
72+
It is guaranteed that every call to ping uses a strictly larger value of t than
73+
before. Based on the above condition, the maximal number of elements in our sliding
74+
window would be 3000, which is also the maximal time difference between the head
75+
and the tail elements.
76+
77+
- Time Complexity: O(1)
78+
- The main time complexity of our ping(t) function lies in the loop, which
79+
in the worst case would run 3000 iterations to pop out all outdated elements
80+
and in the best case a single iteration.
81+
- Therefore, for a single invocation of ping() functions, its
82+
time complexity if O(3000) = O(1).
83+
- If we assume that there is a ping call at each timestamp, then the cost of
84+
ping() is further amortized, where at each invocation, we would only need to
85+
pop out a single element, once the sliding window reaches its upper bound.
86+
- Space Complexity: O(1)
87+
- As we estimated before, the maximal size of our sliding window is 3000,
88+
which is a constant.
89+
"""
90+
91+
def __init__(self):
92+
self.slide_window = deque()
93+
self.length = 0
94+
95+
def ping(self, t: int) -> int:
96+
self.slide_window.append(t)
97+
self.length += 1
98+
99+
while self.slide_window[0] < t - 3000:
100+
self.slide_window.popleft()
101+
self.length -= 1
102+
103+
return self.length
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import unittest
2+
3+
from number_of_recent_calls import RecentCounter, RecentCounterOfficial
4+
5+
6+
class TestRecentCounter(unittest.TestCase):
7+
def test_example_1(self):
8+
recent_counter = RecentCounter()
9+
for t, expected in [(1, 1), (100, 2), (3001, 3), (3002, 3)]:
10+
assert recent_counter.ping(t=t) == expected
11+
12+
recent_counter_official = RecentCounterOfficial()
13+
for t, expected in [(1, 1), (100, 2), (3001, 3), (3002, 3)]:
14+
assert recent_counter_official.ping(t=t) == expected

0 commit comments

Comments
 (0)