Skip to content

Commit 232aace

Browse files
committed
Update prep alien dict, max swap
1 parent 874abc8 commit 232aace

File tree

3 files changed

+142
-0
lines changed

3 files changed

+142
-0
lines changed

2024/meta/prep.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1340,3 +1340,71 @@ def dfs(x, y, index):
13401340
max_island = max(max_island, new_island_size)
13411341

13421342
return max_island
1343+
1344+
1345+
############# 269. Alien Dictionary ############
1346+
from collections import defaultdict, deque
1347+
class Solution:
1348+
def alienOrder(self, words: List[str]) -> str:
1349+
"""
1350+
Detecting Cycle with Khan's Algorithm: If there is a cycle in the graph then result will not include all the nodes in the graph,
1351+
result will return only some of the nodes. To check if there is a cycle,
1352+
you just need to check whether the length of result is equal to the number of nodes in the graph, n.
1353+
"""
1354+
# Step 1: Create a graph and in-degree count for each character
1355+
graph = defaultdict(set)
1356+
in_degree = {char: 0 for word in words for char in word} # can't use defaultdict(int) because BFS algorithm below adds neighbor if indegree is zero
1357+
1358+
# Step 2: Build the graph by comparing adjacent words
1359+
for i in range(len(words) - 1):
1360+
word1, word2 = words[i], words[i + 1]
1361+
min_len = min(len(word1), len(word2))
1362+
1363+
# Check if the second word is a prefix of the first one (invalid case)
1364+
if word1[:min_len] == word2[:min_len] and len(word1) > len(word2):
1365+
return ""
1366+
1367+
# Compare characters in the two words to establish the ordering
1368+
for j in range(min_len):
1369+
if word1[j] != word2[j]:
1370+
if word2[j] not in graph[word1[j]]:
1371+
graph[word1[j]].add(word2[j])
1372+
in_degree[word2[j]] += 1
1373+
break
1374+
1375+
# Step 3: Perform topological sort using Kahn's algorithm (BFS)
1376+
zero_in_degree_queue = deque([char for char in in_degree if in_degree[char] == 0])
1377+
result = []
1378+
1379+
while zero_in_degree_queue:
1380+
char = zero_in_degree_queue.popleft()
1381+
result.append(char)
1382+
1383+
# Decrease the in-degree of adjacent nodes
1384+
for neighbor in graph[char]:
1385+
in_degree[neighbor] -= 1
1386+
if in_degree[neighbor] == 0:
1387+
zero_in_degree_queue.append(neighbor)
1388+
1389+
# If we have processed all characters, return the result
1390+
1391+
# check for cycle, if
1392+
if len(result) == len(in_degree):
1393+
return "".join(result)
1394+
else:
1395+
return ""
1396+
1397+
############# 670. Maximum Swap ############
1398+
class Solution:
1399+
def maximumSwap(self, num: int) -> int:
1400+
num_str = list(str(num))
1401+
last_seen = {}
1402+
for i, c in enumerate(num_str):
1403+
last_seen[int(c)] = i
1404+
1405+
for i, c in enumerate(num_str):
1406+
for d in range(9, int(c), -1):
1407+
if d in last_seen and last_seen[d] > i: # if there's a bigger digit after current index i, can make swap
1408+
num_str[i], num_str[last_seen[d]] = num_str[last_seen[d]], num_str[i]
1409+
return int(''.join(num_str)) # return immediately since we can only make one swap
1410+
return num # not found return original number
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
"""
2+
Khan's algorithm with cycle detection (summary)
3+
Step 1: Compute In-degree: First we create compute a lookup for the in-degrees of every node. In this particular Leetcode problem, each node has a unique integer identifier, so we can simply store all the in-degrees values using a list where indegree[i] tells us the in-degree of node i.
4+
Step 2: Keep track of all nodes with in-degree of zero: If a node has an in-degree of zero it means it is a course that we can take right now. There are no other courses that it depends on. We create a queue q of all these nodes that have in-degree of zero. At any step of Khan's algorithm, if a node is in q then it is guaranteed that it's "safe to take this course" because it does not depend on any courses that "we have not taken yet".
5+
Step 3: Delete node and edges, then repeat: We take one of these special safe courses x from the queue q and conceptually treat everything as if we have deleted the node x and all its outgoing edges from the graph g. In practice, we don't need to update the graph g, for Khan's algorithm it is sufficient to just update the in-degree value of its neighbours to reflect that this node no longer exists.
6+
This step is basically as if a person took and passed the exam for course x, and now we want to update the other courses dependencies to show that they don't need to worry about x anymore.
7+
Step 4: Repeat: When we removing these edges from x, we are decreasing the in-degree of x's neighbours; this can introduce more nodes with an in-degree of zero. During this step, if any more nodes have their in-degree become zero then they are added to q. We repeat step 3 to process these nodes. Each time we remove a node from q we add it to the final topological sort list result.
8+
Step 5. Detecting Cycle with Khan's Algorithm: If there is a cycle in the graph then result will not include all the nodes in the graph, result will return only some of the nodes. To check if there is a cycle, you just need to check whether the length of result is equal to the number of nodes in the graph, n.
9+
Why does this work?: Suppose there is a cycle in the graph: x1 -> x2 -> ... -> xn -> x1, then none of these nodes will appear in the list because their in-degree will not reach 0 during Khan's algorithm. Each node xi in the cycle can't be put into the queue q because there is always some other predecessor node x_(i-1) with an edge going from x_(i-1) to xi preventing this from happening.
10+
"""
11+
12+
from collections import defaultdict, deque
13+
class Solution:
14+
def alienOrder(self, words: List[str]) -> str:
15+
"""
16+
Detecting Cycle with Khan's Algorithm: If there is a cycle in the graph then result will not include all the nodes in the graph,
17+
result will return only some of the nodes. To check if there is a cycle,
18+
you just need to check whether the length of result is equal to the number of nodes in the graph, n.
19+
"""
20+
# Step 1: Create a graph and in-degree count for each character
21+
graph = defaultdict(set)
22+
in_degree = {char: 0 for word in words for char in word} # can't use defaultdict(int) because BFS algorithm below adds neighbor if indegree is zero
23+
24+
# Step 2: Build the graph by comparing adjacent words
25+
for i in range(len(words) - 1):
26+
word1, word2 = words[i], words[i + 1]
27+
min_len = min(len(word1), len(word2))
28+
29+
# Check if the second word is a prefix of the first one (invalid case)
30+
if word1[:min_len] == word2[:min_len] and len(word1) > len(word2):
31+
return ""
32+
33+
# Compare characters in the two words to establish the ordering
34+
for j in range(min_len):
35+
if word1[j] != word2[j]:
36+
if word2[j] not in graph[word1[j]]:
37+
graph[word1[j]].add(word2[j])
38+
in_degree[word2[j]] += 1
39+
break
40+
41+
# Step 3: Perform topological sort using Kahn's algorithm (BFS)
42+
zero_in_degree_queue = deque([char for char in in_degree if in_degree[char] == 0])
43+
result = []
44+
45+
while zero_in_degree_queue:
46+
char = zero_in_degree_queue.popleft()
47+
result.append(char)
48+
49+
# Decrease the in-degree of adjacent nodes
50+
for neighbor in graph[char]:
51+
in_degree[neighbor] -= 1
52+
if in_degree[neighbor] == 0:
53+
zero_in_degree_queue.append(neighbor)
54+
55+
# If we have processed all characters, return the result
56+
57+
# check for cycle, if
58+
if len(result) == len(in_degree):
59+
return "".join(result)
60+
else:
61+
return ""

math/670. Maximum Swap.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
class Solution:
2+
def maximumSwap(self, num: int) -> int:
3+
num_str = list(str(num))
4+
last_seen = {}
5+
for i, c in enumerate(num_str):
6+
last_seen[int(c)] = i
7+
8+
for i, c in enumerate(num_str):
9+
for d in range(9, int(c), -1):
10+
if d in last_seen and last_seen[d] > i: # if there's a bigger digit after current index i, can make swap
11+
num_str[i], num_str[last_seen[d]] = num_str[last_seen[d]], num_str[i]
12+
return int(''.join(num_str)) # return immediately since we can only make one swap
13+
return num # not found return original number

0 commit comments

Comments
 (0)