Skip to content

Commit 9257f55

Browse files
committed
solve Number of Recent Calls
1 parent 5426d69 commit 9257f55

11 files changed

+510
-0
lines changed

problems/evaluate_division.py

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
from collections import defaultdict
2+
from typing import List
3+
4+
5+
class OfficialSolution:
6+
"""
7+
== Overview ==
8+
The problem can be solved with 2 important data structures, namely Graph and Union-
9+
Find.
10+
"""
11+
12+
def calcEquation(
13+
self, equations: List[List[str]], values: List[float], queries: List[List[str]]
14+
) -> List[float]:
15+
"""
16+
== Approach 1: Path Search in Graph ==
17+
== Intuition ==
18+
First, let us look at the example given in the problem description. Given two
19+
equations, namely a/b=2, b/c=3, we could derive the following equations:
20+
- 1) b/a = 1/2, c/b = 1/3
21+
- 2) a/c = a/b . b/c = 6
22+
Each division implies the reverse of the division, which is how we derive the
23+
equations in (1). While by chaining up equations, we could obtain new equations
24+
in (2).
25+
26+
We could reformulate the equations with the graph data structure, where each
27+
variable can be represented as a node in the graph, and the division
28+
relationship between variables can be modeled as edge with direction and weight.
29+
30+
The direction of edge indicates the order of division, and the weight of edge
31+
indicates the result of division.
32+
33+
With the above formulation, we then can convert the initial equations into the
34+
following graph:
35+
36+
a/b=2 b/c=3
37+
a ------> b -------> c
38+
a <------- b <------ c
39+
b/a=1/2 c/b=1/3
40+
41+
To evaluate the query (e.g. a/c = ?) is equivalent to performing two tasks on
42+
the graph:
43+
1. Find if there exists a path between the two entities.
44+
2. If so, calculate the cumulative products along the paths.
45+
46+
In the above example (a/c = ?), we could find a path between them, and the
47+
cumulative products are 6. As a result, we can conclude that the result of
48+
a/c is 2.3 = 6.
49+
50+
== Algorithm ==
51+
As one can see, we just transform the problem into a path searching problem in
52+
a graph.
53+
54+
More precisely, we can reinterpret the problem as "given two nodes, we are asked
55+
to check if there exists a path between them. If so, we should return the
56+
cumulative products along the path as the result."
57+
58+
Given the above problem statement, it seems intuitive that one could apply the
59+
backtracking algorithm, or sometimes people might call it DFS (Depth-First
60+
Search).
61+
62+
Essentially, we can break down the algorithm into 2 steps overall:
63+
Step 1. We build the graph out of the list of input equations.
64+
- Each equation corresponds to two edges in the graph.
65+
Step 2. Once the graph is built, we then can evaluate the query one by one.
66+
- The evaluation of the query is done via searching the path between the
67+
given two variables.
68+
- Other than the above searching operation, we need to handle two
69+
exceptional cases as follows:
70+
- Case 1. If either of the nodes does not exist in the graph, i.e. the
71+
variables did not appear in any of the input equations, then we can
72+
assert that no path exists.
73+
- Case 2. If the origin and the destination are the same node, i.e. a/a,
74+
we can assume that therre exists an invisible self-loop path for each
75+
node and the result is 1.
76+
77+
Note: With the built graph, one could also apply the BFS (Breadth-First Search)
78+
algorithm, as opposed to the DFS algorithm we employed.
79+
80+
However, the essence of the solution remains the same, i.e. we are searching for
81+
a path in a graph.
82+
83+
== Complexity Analysis ==
84+
Let N be the number of input equations and M be the number of queries.
85+
Time Complexity:
86+
O(MN)
87+
- First of all, we iterate through the equations to build a graph. Each
88+
equation takes O(1) time to process. Therefore, this step will take O(N)
89+
time in total.
90+
- For each query, we need to traverse the graph. In the worst case, we might
91+
need to traverse the entire graph, which could take O(N). Hence, in total,
92+
the evaluation of queries could take M*O(N) = O(MN).
93+
- To sum up, the overall time complexity of the algorithm is
94+
O(N) + O(MN) = O(MN).
95+
96+
Space Complexity:
97+
O(N)
98+
- We build a graph out of the equations. In the worst case where there is no
99+
overlapping among the equations, we would have N edges and 2N nodes in the
100+
graph. Therefore, the space complexity of the graph is O(N+2N)=O(3N)=O(N).
101+
- Since we employ the recursion in the backtracking, we would consume
102+
additional memory in the function call stack, which could amount to O(N)
103+
space.
104+
- In addition, we used a set
105+
"""
106+
107+
# Graph as Adjacency Lists
108+
graph = defaultdict(defaultdict)
109+
110+
for (numerator, denominator), value in zip(equations, values):
111+
graph[numerator][denominator] = value
112+
graph[denominator][numerator] = 1 / value
113+
114+
def dfs(curr: str, end: str, visited=None, cum_weight: float = 1.0) -> float:
115+
if visited is None:
116+
visited = set()
117+
118+
if curr == end:
119+
return cum_weight
120+
121+
visited.add(curr)
122+
123+
for node, value in graph[curr].items():
124+
if node in visited:
125+
continue
126+
127+
ans = dfs(
128+
curr=node, end=end, visited=visited, cum_weight=cum_weight * value
129+
)
130+
131+
if ans != -1.0:
132+
return ans
133+
134+
visited.remove(curr)
135+
136+
return -1.0
137+
138+
ans = []
139+
for (start, end) in queries:
140+
if start not in graph:
141+
ans.append(-1.0)
142+
else:
143+
ans.append(dfs(curr=start, end=end))
144+
145+
return ans
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Definition for a binary tree node.
2+
class TreeNode:
3+
def __init__(self, x):
4+
self.val = x
5+
self.left = None
6+
self.right = None
7+
8+
9+
class Solution:
10+
def getTargetCopy(
11+
self, original: TreeNode, cloned: TreeNode, target: TreeNode
12+
) -> TreeNode:
13+
pass

problems/first_missing_positive.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
from typing import List
2+
3+
4+
class OfficialSolution:
5+
def firstMissingPositive(self, nums: List[int]) -> int:
6+
"""
7+
== Approach 1: Index as a hash key. ==
8+
== Data Clean Up ==
9+
First of all, let's get rid of negative numbers and zeros since there is no
10+
need of them. One could get rid of all numbers larger than n as well, since the
11+
first missing positive is for sure smaller or equal to n+1. The case when the
12+
first missing positive is equal to n+1 will be treated separately.
13+
What does it mean - to get rid of, if one has to keep O(N) time complexity and
14+
hence could not pop unwanted elements out? Let's just replace all these by 1s.
15+
To ensure that the first missing positive is not 1, one has to verify the
16+
presence of 1 before proceeding to this operation.
17+
== How to solve in-place ==
18+
Now that we we have an array which contains only positive numbers in a range
19+
from 1 to n, and the problem is to find a first missing positive in O(N) time
20+
and constant space.
21+
That would be simple, if one would be allowed to have a hash-map positive number
22+
-> its presence for the array. Sort of "dirty workaround" solution would be to
23+
allocate a string hash_str with n zeros, and use it as a sort of hash map by
24+
changing hash_str[i] to 1 each time one meets number i in the array.
25+
Let's not use this solution, but just take away a pretty nice idea to use index
26+
as a hash-key for a positive number.
27+
The final idea is to use index in nums as a hash key and sign of the element as
28+
a hash value which is presence detector.
29+
For example, negative sign of nums[2] element means that number 2 is present in
30+
nums. The positive sign of nums[3] element means that number 3 is not present
31+
(missing) in nums.
32+
"""
33+
n = len(nums)
34+
one_present = False
35+
36+
# Replace all numbers < 1 and > n with 1.
37+
for i in range(n):
38+
if nums[i] == 1:
39+
one_present = True
40+
if nums[i] < 1 or nums[i] > n:
41+
nums[i] = 1
42+
43+
# If 1 is not present, return it.
44+
if one_present is False:
45+
return 1
46+
47+
# Now each element of nums is in [1,n]
48+
# Use index as hash and flip sign to negative.
49+
for i in range(n):
50+
if nums[abs(nums[i]) - 1] > 0:
51+
nums[abs(nums[i]) - 1] *= -1
52+
53+
# Smallest index of positive element is missing element.
54+
for i in range(n + 1):
55+
if i == n or nums[i] > 0:
56+
return i + 1

problems/largest_component_size_by_common_factor.py

Whitespace-only changes.

problems/stream_of_characters.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from collections import deque
2+
from typing import List
3+
4+
5+
class StreamChecker:
6+
def __init__(self, words: List[str]):
7+
trie = {"root": {}}
8+
for word in words:
9+
node = trie["root"]
10+
for ch in word:
11+
if ch not in node:
12+
node[ch] = {}
13+
node = node[ch]
14+
node["$"] = 1
15+
16+
self.trie = trie
17+
self.stream = deque([])
18+
19+
def query(self, letter: str) -> bool:
20+
self.stream.appendleft(letter)
21+
node = self.trie
22+
for ch in self.stream:
23+
if "$" in node:
24+
return True
25+
if not ch in node:
26+
return False
27+
node = node[ch]
28+
return "$" in node
29+
30+
31+
# Your StreamChecker object will be instantiated and called as such:
32+
# obj = StreamChecker(words)
33+
# param_1 = obj.query(letter)

0 commit comments

Comments
 (0)