Skip to content

Commit aa1e4d9

Browse files
committed
Reine
1 parent be2f788 commit aa1e4d9

10 files changed

Lines changed: 603 additions & 38 deletions
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# https://leetcode.com/problems/kth-ancestor-of-a-tree-node/description/
2+
3+
"""
4+
You are given a tree with n nodes numbered from 0 to n - 1 in the form of a parent array parent where parent[i] is the parent of ith node. The root of the tree is node 0. Find the kth ancestor of a given node.
5+
The kth ancestor of a tree node is the kth node in the path from that node to the root node.
6+
Implement the TreeAncestor class:
7+
TreeAncestor(int n, int[] parent) Initializes the object with the number of nodes in the tree and the parent array.
8+
int getKthAncestor(int node, int k) return the kth ancestor of the given node node. If there is no such ancestor, return -1.
9+
10+
Example 1:
11+
Input
12+
["TreeAncestor", "getKthAncestor", "getKthAncestor", "getKthAncestor"]
13+
[[7, [-1, 0, 0, 1, 1, 2, 2]], [3, 1], [5, 2], [6, 3]]
14+
Output
15+
[null, 1, 0, -1]
16+
17+
Explanation
18+
TreeAncestor treeAncestor = new TreeAncestor(7, [-1, 0, 0, 1, 1, 2, 2]);
19+
treeAncestor.getKthAncestor(3, 1); // returns 1 which is the parent of 3
20+
treeAncestor.getKthAncestor(5, 2); // returns 0 which is the grandparent of 5
21+
treeAncestor.getKthAncestor(6, 3); // returns -1 because there is no such ancestor
22+
23+
Constraints:
24+
1 <= k <= n <= 5 * 104
25+
parent.length == n
26+
parent[0] == -1
27+
0 <= parent[i] < n for all 0 < i < n
28+
0 <= node < n
29+
There will be at most 5 * 104 queries.
30+
"""
31+
32+
33+
class TreeAncestor:
34+
35+
def __init__(self, n: int, parent: List[int]):
36+
m = 1 + int(log2(n)) # at most 16 for this problem
37+
self.dp = [[-1] * m for _ in range(n)] # ith node's 2^j parent
38+
for j in range(m):
39+
for i in range(n):
40+
if j == 0:
41+
self.dp[i][0] = parent[i] # 2^0 parent
42+
elif self.dp[i][j - 1] != -1:
43+
self.dp[i][j] = self.dp[self.dp[i][j - 1]][j - 1]
44+
45+
def getKthAncestor(self, node: int, k: int) -> int:
46+
while k > 0 and node != -1:
47+
i = int(log2(k))
48+
node = self.dp[node][i]
49+
k -= 1 << i
50+
return node
51+
52+
'''
53+
For node 0, 1, ..., n-1, we define a matrix self.dp[][] whose i, jth element indicates the ith node's 2^j parent. Here, i = 0, 1, ..., n-1 and j = 0, 1, ..., int(log2(n)). An important recursive relationship is that
54+
55+
self.dp[i][j] = self.dp[self.dp[i][j-1]][j-1].
56+
57+
In other words, ith node's 2^j parent is ith node's 2^(j-1) parent's 2^(j-1) parent. In this way, lookup is guarenteed to complete in O(logN) time. Note that it takes O(NlogN) to build self.dp.
58+
59+
Time complexity O(NlogN) to pre-processing the tree and O(logN) for each equery thereafter.
60+
'''
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
# https://leetcode.com/problems/number-of-ways-to-assign-edge-weights-ii/description/
2+
3+
4+
"""
5+
There is an undirected tree with n nodes labeled from 1 to n, rooted at node 1. The tree is represented by a 2D integer array edges of length n - 1, where edges[i] = [ui, vi] indicates that there is an edge between nodes ui and vi.
6+
Initially, all edges have a weight of 0. You must assign each edge a weight of either 1 or 2.
7+
The cost of a path between any two nodes u and v is the total weight of all edges in the path connecting them.
8+
You are given a 2D integer array queries. For each queries[i] = [ui, vi], determine the number of ways to assign weights to edges in the path such that the cost of the path between ui and vi is odd.
9+
Return an array answer, where answer[i] is the number of valid assignments for queries[i].
10+
Since the answer may be large, apply modulo 109 + 7 to each answer[i].
11+
Note: For each query, disregard all edges not in the path between node ui and vi.
12+
13+
Example 1:
14+
Input: edges = [[1,2]], queries = [[1,1],[1,2]]
15+
Output: [0,1]
16+
Explanation:
17+
Query [1,1]: The path from Node 1 to itself consists of no edges, so the cost is 0. Thus, the number of valid assignments is 0.
18+
Query [1,2]: The path from Node 1 to Node 2 consists of one edge (1 → 2). Assigning weight 1 makes the cost odd, while 2 makes it even. Thus, the number of valid assignments is 1.
19+
20+
Example 2:
21+
Input: edges = [[1,2],[1,3],[3,4],[3,5]], queries = [[1,4],[3,4],[2,5]]
22+
Output: [2,1,4]
23+
Explanation:
24+
Query [1,4]: The path from Node 1 to Node 4 consists of two edges (1 → 3 and 3 → 4). Assigning weights (1,2) or (2,1) results in an odd cost. Thus, the number of valid assignments is 2.
25+
Query [3,4]: The path from Node 3 to Node 4 consists of one edge (3 → 4). Assigning weight 1 makes the cost odd, while 2 makes it even. Thus, the number of valid assignments is 1.
26+
Query [2,5]: The path from Node 2 to Node 5 consists of three edges (2 → 1, 1 → 3, and 3 → 5). Assigning (1,2,2), (2,1,2), (2,2,1), or (1,1,1) makes the cost odd. Thus, the number of valid assignments is 4.
27+
28+
29+
Constraints:
30+
2 <= n <= 105
31+
edges.length == n - 1
32+
edges[i] == [ui, vi]
33+
1 <= queries.length <= 105
34+
queries[i] == [ui, vi]
35+
1 <= ui, vi <= n
36+
edges represents a valid tree.
37+
"""
38+
39+
40+
class Solution:
41+
MOD = 10**9 + 7
42+
43+
def modPow(self, a, b):
44+
ans = 1
45+
while b > 0:
46+
if b & 1:
47+
ans = (ans * a) % self.MOD
48+
a = (a * a) % self.MOD
49+
b >>= 1
50+
return ans
51+
52+
def dfs(self, node, parent):
53+
self.up[node][0] = parent
54+
55+
for j in range(1, self.LOG):
56+
self.up[node][j] = self.up[self.up[node][j - 1]][j - 1]
57+
58+
for neighbour in self.adj[node]:
59+
if neighbour == parent:
60+
continue
61+
62+
self.depth[neighbour] = self.depth[node] + 1
63+
self.dfs(neighbour, node)
64+
65+
def lca(self, u, v):
66+
if self.depth[u] < self.depth[v]:
67+
u, v = v, u
68+
69+
diff = self.depth[u] - self.depth[v]
70+
71+
for j in range(self.LOG - 1, -1, -1):
72+
if diff & (1 << j):
73+
u = self.up[u][j]
74+
75+
if u == v:
76+
return u
77+
78+
for j in range(self.LOG - 1, -1, -1):
79+
if self.up[u][j] != self.up[v][j]:
80+
u = self.up[u][j]
81+
v = self.up[v][j]
82+
return self.up[u][0]
83+
84+
def assignEdgeWeights(
85+
self, edges: List[List[int]], queries: List[List[int]]
86+
) -> List[int]:
87+
n = len(edges) + 1
88+
89+
self.LOG = 1
90+
while (1 << self.LOG) <= n:
91+
self.LOG += 1
92+
93+
self.adj = [[] for _ in range(n + 1)]
94+
95+
for u, v in edges:
96+
self.adj[u].append(v)
97+
self.adj[v].append(u)
98+
99+
self.depth = [0] * (n + 1)
100+
self.up = [[0] * self.LOG for _ in range(n + 1)]
101+
102+
self.dfs(1, 0)
103+
ans = []
104+
105+
for u, v in queries:
106+
L = self.lca(u, v)
107+
dist = self.depth[u] + self.depth[v] - 2 * self.depth[L]
108+
109+
if dist == 0:
110+
ans.append(0)
111+
else:
112+
ans.append(self.modPow(2, dist - 1))
113+
return ans
114+
115+
"""
116+
Complexity Analysis
117+
Let n be the number of nodes in the tree, and let m be the number of queries.
118+
119+
Time complexity: O(nlogn+mlogn).
120+
Building the binary lifting table requires O(nlogn) time. Each LCA query takes O(logn) time, resulting in a total query cost of O(mlogn).
121+
122+
Space complexity: O(nlogn).
123+
The binary lifting table stores O(logn) ancestors for each node, requiring O(nlogn) space.
124+
"""
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[Video](https://www.youtube.com/watch?v=WTtuOj6FXE8&t=1s)
2+
3+
[Video 2](https://www.youtube.com/watch?v=k5tS37rLCBs)
4+

Graph/Dijkastra Algo/743. Network Delay Time.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ def networkDelayTime(self, times: List[List[int]], n: int, k: int) -> int:
3131

3232
adj=[[] for _ in range(n+1)]
3333

34-
for u,v, w in times:
34+
for u,v,w in times:
3535
adj[u].append((v,w))
3636

3737
h=[(0,k)]
@@ -57,9 +57,13 @@ def networkDelayTime(self, times: List[List[int]], n: int, k: int) -> int:
5757
# Here N is the number of nodes and E is the number of total edges in the given network.
5858
# Time complexity: O(N+ElogN)
5959
# Dijkstra's Algorithm takes O(ElogN). Finding the minimum time required in signalReceivedAt takes O(N).
60-
# The maximum number of vertices that could be added to the priority queue is E. Thus, push and pop operations on the priority queue take O(logE) time. The value of E can be at most N⋅(N−1). Therefore, O(logE) is equivalent to O(logN 2 ) which in turn equivalent to O(2⋅logN). Hence, the time complexity for priority queue operations equals O(logN).
61-
# Although the number of vertices in the priority queue could be equal to E, we will only visit each vertex only once. If we encounter a vertex for the second time, then currNodeTime will be greater than signalReceivedAt[currNode], and we can continue to the next vertex in the priority queue. Hence, in total E edges will be traversed and for each edge, there could be one priority queue insertion operation.
60+
# The maximum number of vertices that could be added to the priority queue is E. Thus, push and pop operations on the priority queue take O(logE) time.
61+
# The value of E can be at most N⋅(N−1). Therefore, O(logE) is equivalent to O(logN^2 ) which in turn equivalent to O(2⋅logN).
62+
# Hence, the time complexity for priority queue operations equals O(logN).
63+
# Although the number of vertices in the priority queue could be equal to E, we will only visit each vertex only once.
64+
# If we encounter a vertex for the second time, then currNodeTime will be greater than signalReceivedAt[currNode], and we can continue to the next vertex in the priority queue.
65+
# Hence, in total E edges will be traversed and for each edge, there could be one priority queue insertion operation.
6266
# Hence, the time complexity is equal to O(N+ElogN).
6367

6468
# Space complexity: O(N+E)
65-
# Building the adjacency list will take O(E) space. Dijkstra's algorithm takes O(E) space for priority queue because each vertex could be added to the priority queue N−1 time which makes it N(N−1) and O(N 2) is equivalent to O(E). signalReceivedAt takes O(N) space.
69+
# Building the adjacency list will take O(E) space. Dijkstra's algorithm takes O(E) space for priority queue because each vertex could be added to the priority queue N−1 time which makes it N*(N−1) and O(N^2) is equivalent to O(E). signalReceivedAt takes O(N) space.

Graph/Topological Sort/269. Alien Dictionary.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,5 @@ def alienOrder(self, words: List[str]) -> str:
7070
# O(T) where T = total number of characters in input
7171
# Final Total Time Complexity: O(T+E)
7272

73-
74-
7573
# Time Complexity:O(T+E)(effectively O(T))
76-
# Space Complexity: O(U+E)(effectively O(1))
74+
# Space Complexity: O(U+E)(effectively O(1))

Heap/347. Top K Frequent Elements.py

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -40,20 +40,31 @@ def topKFrequent(self, nums: List[int], k: int) -> List[int]:
4040

4141

4242
from collections import Counter
43+
from typing import List
4344

44-
def topKFrequent(nums, k):
45-
freq = Counter(nums)
46-
buckets = [[] for _ in range(len(nums) + 1)]
4745

48-
for num, count in freq.items():
49-
buckets[count].append(num)
46+
class Solution:
47+
def topKFrequent(self, nums: List[int], k: int) -> List[int]:
48+
freq = Counter(nums)
49+
50+
# buckets[i] contains all numbers that appear exactly i times
51+
buckets = [[] for _ in range(len(nums) + 1)]
52+
53+
for num, count in freq.items():
54+
buckets[count].append(num)
55+
56+
result = []
57+
58+
# Traverse from highest frequency to lowest frequency
59+
for count in range(len(nums), 0, -1):
60+
for num in buckets[count]:
61+
result.append(num)
62+
63+
if len(result) == k:
64+
return result
65+
66+
return result
5067

51-
result = []
52-
for i in range(len(buckets) - 1, -1, -1):
53-
for num in buckets[i]:
54-
result.append(num)
55-
if len(result) == k:
56-
return result
5768

5869
# Bucket Sort
5970
# O(n)
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# https://leetcode.com/problems/min-cost-to-connect-all-points/description/
2+
3+
4+
"""
5+
You are given an array points representing integer coordinates of some points on a 2D-plane, where points[i] = [xi, yi].
6+
The cost of connecting two points [xi, yi] and [xj, yj] is the manhattan distance between them: |xi - xj| + |yi - yj|, where |val| denotes the absolute value of val.
7+
Return the minimum cost to make all points connected. All points are connected if there is exactly one simple path between any two points.
8+
9+
Example 1:
10+
Input: points = [[0,0],[2,2],[3,10],[5,2],[7,0]]
11+
Output: 20
12+
Explanation:
13+
We can connect the points as shown above to get the minimum cost of 20.
14+
Notice that there is a unique path between every pair of points.
15+
16+
Example 2:
17+
Input: points = [[3,12],[-2,5],[-4,1]]
18+
Output: 18
19+
20+
Constraints:
21+
1 <= points.length <= 1000
22+
-106 <= xi, yi <= 106
23+
All pairs (xi, yi) are distinct.
24+
"""
25+
26+
27+
class Solution:
28+
def minCostConnectPoints(self, points: List[List[int]]) -> int:
29+
n = len(points)
30+
h = [(0, 0)] # cost, point
31+
in_mst = [False] * n
32+
mst_cost = 0
33+
edges_used = 0
34+
while edges_used < n:
35+
w, curr_node = heappop(h)
36+
37+
if in_mst[curr_node]:
38+
continue
39+
in_mst[curr_node] = True
40+
mst_cost += w
41+
edges_used += 1
42+
for next_node in range(n):
43+
if not in_mst[next_node]:
44+
next_w = abs(points[curr_node][0] - points[next_node][0]) + abs(
45+
points[curr_node][1] - points[next_node][1]
46+
)
47+
heappush(h, (next_w, next_node))
48+
return mst_cost
49+
50+
"""
51+
Complexity Analysis
52+
53+
If N is the number of points in the input array.
54+
55+
Time complexity: O(N^2⋅log(N)).
56+
In the worst-case, we push/pop N⋅(N−1)/2≈N^2 edges of our graph in the heap. Each push/pop operation takes O(log(N^2))≈log(N) time.
57+
Thus, the overall time complexity is O(N^2 ⋅log(N)).
58+
59+
Space complexity: O(N^2).
60+
In the worst-case, we push N⋅(N−1)/2≈N^2 edges into the heap.
61+
We use an array inMST of size N to mark which nodes are included in MST.
62+
Thus, the overall space complexity is O(N^2+N)≈O(N^2).
63+
"""
64+
65+
class Solution:
66+
def minCostConnectPoints(self, points: List[List[int]]) -> int:
67+
n = len(points)
68+
mst_cost = 0
69+
edges_used = 0
70+
71+
# Track nodes which are visited.
72+
in_mst = [False] * n
73+
74+
min_dist = [math.inf] * n
75+
min_dist[0] = 0
76+
77+
while edges_used < n:
78+
curr_min_edge = math.inf
79+
curr_node = -1
80+
81+
# Pick least weight node which is not in MST.
82+
for node in range(n):
83+
if not in_mst[node] and curr_min_edge > min_dist[node]:
84+
curr_min_edge = min_dist[node]
85+
curr_node = node
86+
87+
mst_cost += curr_min_edge
88+
edges_used += 1
89+
in_mst[curr_node] = True
90+
91+
# Update adjacent nodes of current node.
92+
for next_node in range(n):
93+
weight = abs(points[curr_node][0] - points[next_node][0]) + abs(
94+
points[curr_node][1] - points[next_node][1]
95+
)
96+
97+
if not in_mst[next_node] and min_dist[next_node] > weight:
98+
min_dist[next_node] = weight
99+
100+
return mst_cost
101+
102+
'''
103+
Complexity Analysis
104+
If N is the number of points in the input array.
105+
106+
Time complexity: O(N^2).
107+
We pick all N nodes one by one to include in the MST. Picking each node takes O(N) time and after picking a node, we iterate over all of its adjacent nodes, which also takes O(N) time.
108+
Thus, the overall time complexity is O(N⋅(N+N))=O(N^2).
109+
Space complexity: O(N).
110+
111+
We use two arrays each of size N, inMST and minDist.
112+
Thus, the overall space complexity is O(N+N)=O(N).
113+
'''

0 commit comments

Comments
 (0)