Skip to content

Commit c4047bc

Browse files
authored
Merge pull request #1122 from yolophg/main
[Helena] Week 15
2 parents 3c451c0 + 7a9762f commit c4047bc

File tree

4 files changed

+152
-0
lines changed

4 files changed

+152
-0
lines changed

alien-dictionary/yolophg.py

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Time Complexity: O(N + K) - where N is total number of characters in all words, and K is total number of unique character relations (edges) we derive
2+
# Space Complexity: O(N + K) - for graph, in-degree map, and heap
3+
4+
class Solution:
5+
def alien_order(self, words):
6+
# adjacency list, u -> set of v
7+
graph = defaultdict(set)
8+
# how many chars come before this one
9+
in_degree = {}
10+
11+
# initialize in_degree with all unique chars
12+
for word in words:
13+
for char in word:
14+
in_degree[char] = 0
15+
16+
# compare each adjacent pair of words
17+
for i in range(len(words) - 1):
18+
w1, w2 = words[i], words[i + 1]
19+
min_len = min(len(w1), len(w2))
20+
# handle invalid case like ["abc", "ab"]
21+
if len(w1) > len(w2) and w1[:min_len] == w2[:min_len]:
22+
return ""
23+
24+
for j in range(min_len):
25+
if w1[j] != w2[j]:
26+
# first different char tells us the order
27+
if w2[j] not in graph[w1[j]]:
28+
graph[w1[j]].add(w2[j])
29+
in_degree[w2[j]] += 1
30+
# only first different char matters
31+
break
32+
33+
# topological sort (use min heap for smallest lex order)
34+
heap = []
35+
for char in in_degree:
36+
if in_degree[char] == 0:
37+
heapq.heappush(heap, char)
38+
39+
result = []
40+
while heap:
41+
char = heapq.heappop(heap)
42+
result.append(char)
43+
for neighbor in sorted(graph[char]):
44+
in_degree[neighbor] -= 1
45+
if in_degree[neighbor] == 0:
46+
heapq.heappush(heap, neighbor)
47+
48+
# if we added all characters into result, it's valid
49+
if len(result) == len(in_degree):
50+
return ''.join(result)
51+
else:
52+
return ""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Time Complexity: O(n) - where n is the length of the word(addWord)
2+
# Space Complexity: O(N) - where N is the total number of characters inserted
3+
4+
class WordDictionary:
5+
def __init__(self):
6+
# using a trie (prefix tree) to store all the words
7+
self.trie = dict()
8+
9+
def addWord(self, word: str) -> None:
10+
# start from the root of the trie
11+
trie = self.trie
12+
for letter in word:
13+
# if this letter isn't already in the current trie level, add it
14+
if letter not in trie:
15+
trie[letter] = dict()
16+
# move one level deeper
17+
trie = trie[letter]
18+
# mark the end of the word with a special null character
19+
trie['\0'] = dict()
20+
21+
def internal_search(self, trie: dict, index: int, word: str) -> bool:
22+
if index == len(word):
23+
# check if this path ends a valid word
24+
return '\0' in trie
25+
26+
letter = word[index]
27+
28+
# if hit a '.', gotta try all possible paths from here
29+
if letter == '.':
30+
for child in trie.values():
31+
if self.internal_search(child, index + 1, word):
32+
return True
33+
return False
34+
else:
35+
# if the letter exists in the current trie level, keep going
36+
if letter in trie:
37+
return self.internal_search(trie[letter], index + 1, word)
38+
else:
39+
return False
40+
41+
def search(self, word: str) -> bool:
42+
# start the recursive search from index 0 and the root
43+
return self.internal_search(self.trie, 0, word)

subtree-of-another-tree/yolophg.py

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Time Complexity: O(m * n) m: number of nodes in root, n: number of nodes in subRoot, n: number of nodes in subRoot - might call the check function (which is O(n)) on every node in root.
2+
# Space Complexity: O(n) - use a queue for BFS that can hold up to O(n) nodes in the worst case.
3+
4+
class Solution:
5+
def isSubtree(self, root: Optional[TreeNode], subRoot: Optional[TreeNode]) -> bool:
6+
if root is None:
7+
return False
8+
if subRoot is None:
9+
return True
10+
11+
# helper to compare two trees
12+
def check(node1, node2):
13+
if node1 is None and node2 is None:
14+
return True
15+
if node1 is None or node2 is None:
16+
return False
17+
if node1.val != node2.val:
18+
return False
19+
# check left and right recursively
20+
return check(node1.left, node2.left) and check(node1.right, node2.right)
21+
22+
# BFS through the main tree
23+
queue = [root]
24+
while queue:
25+
curr = queue.pop(0)
26+
# if value matches subRoot, check deeper
27+
if curr.val == subRoot.val:
28+
if check(curr, subRoot):
29+
return True
30+
# add child nodes to keep exploring
31+
if curr.left:
32+
queue.append(curr.left)
33+
if curr.right:
34+
queue.append(curr.right)
35+
36+
return False
+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Time Complexity: O(n) - visit every node once during the inorder traversal
2+
# Space Complexity: O(n) - store all node values in an array during traversal
3+
4+
class Solution:
5+
def isValidBST(self, root: Optional[TreeNode]) -> bool:
6+
# helper to do an inorder traversal and return a list of values
7+
def inorder(node):
8+
if not node:
9+
return []
10+
# in-order: left -> current -> right
11+
return inorder(node.left) + [node.val] + inorder(node.right)
12+
13+
# get the in-order traversal of the tree
14+
arr = inorder(root)
15+
16+
# if there are duplicates, it's not a valid BST
17+
if len(arr) != len(set(arr)):
18+
return False
19+
20+
# if it's sorted in strictly increasing order, it's a valid BST
21+
return arr == sorted(arr)

0 commit comments

Comments
 (0)