Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Helena] Week 12 #1062

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions non-overlapping-intervals/yolophg.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Time Complexity: O(N log N)
# (1) sorting the intervals takes O(N log N), and
# (2) iterating through them takes O(N).
# (3) so the overall complexity is O(N log N).
# Space Complexity: O(1) - only use a few extra variables (end and res), so the space usage is constant.

class Solution:
def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int:
# sort intervals by their ending time
intervals.sort(key=lambda x: x[1])

# track the last non-overlapping interval's end
end = float('-inf')
# count the number of intervals we need to remove
res = 0

for interval in intervals:
# if it overlaps with the last interval
if interval[0] < end:
# need to remove this one
res += 1
else:
# otherwise, update the end to the current interval's end
end = interval[1]
return res
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Time Complexity: O(N + E) - go through all nodes & edges.
# Space Complexity: O(N) - store parent info for each node.

class Solution:
def count_components(self, n: int, edges: List[List[int]]) -> int:

# set up the Union-Find structure
parent = [i for i in range(n)] # initially, each node is its own parent
rank = [1] * n # used for optimization in Union operation

# find function (with path compression)
def find(node):
if parent[node] != node:
parent[node] = find(parent[node]) # path compression
return parent[node]

# union function (using rank to keep tree flat)
def union(node1, node2):
root1, root2 = find(node1), find(node2)
if root1 != root2:
if rank[root1] > rank[root2]:
parent[root2] = root1
elif rank[root1] < rank[root2]:
parent[root1] = root2
else:
parent[root2] = root1
rank[root1] += 1
return True # union was successful (i.e., we merged two components)
return False # already in the same component

# connect nodes using Union-Find
for a, b in edges:
union(a, b)

# count unique roots (number of connected components)
return len(set(find(i) for i in range(n)))
29 changes: 29 additions & 0 deletions remove-nth-node-from-end-of-list/yolophg.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Time Complexity: O(N) - go through the list twice (once to count, once to remove).
# Space Complexity: O(1) - only use a few extra variables.

class Solution:
def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
# count the total number of nodes in the list
N = 0
curr = head
while curr:
N += 1
curr = curr.next

# find the position of the node to remove (from the start)
node_to_remove = N - n

# if need to remove the first node, just return the second node as the new head
if node_to_remove == 0:
return head.next

# traverse again to find the node right before the one we need to remove
curr = head
for i in range(N - 1):
if (i + 1) == node_to_remove:
# remove the nth node by skipping it
curr.next = curr.next.next
break
curr = curr.next

return head
19 changes: 19 additions & 0 deletions same-tree/yolophg.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Time Complexity: O(N) - visit each node once.
# Space Complexity: O(N) in the worst case (skewed tree), O(log N) in the best case (balanced tree).

class Solution:
def isSameTree(self, p: Optional[TreeNode], q: Optional[TreeNode]) -> bool:
# if both trees are empty, they are obviously the same
if p is None and q is None:
return True

# if one tree is empty but the other is not, they can't be the same
if p is None or q is None:
return False

# if values of the current nodes are different, trees are not the same
if p.val != q.val:
return False

# recursively check both left and right subtrees
return self.isSameTree(p.left, q.left) and self.isSameTree(p.right, q.right)
58 changes: 58 additions & 0 deletions serialize-and-deserialize-binary-tree/yolophg.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Time Complexity:
# (1) serialize(): O(N) because traverse all nodes once.
# (2) deserialize(): O(N) because process each node once.

# Space Complexity:
# (1) serialize(): O(N) to store the serialized string.
# (2) deserialize(): O(N) for the queue and reconstructed tree.

class Codec:
def serialize(self, root):
# store the tree as a list of values (BFS style)
ans = []
if not root:
return ""

queue = [root]
while queue:
cur = queue.pop()
if not cur:
ans.append("n") # use 'n' to represent null nodes
continue
ans.append(str(cur.val)) # add node value as string
queue.append(cur.right) # right first (for consistency in deserialization)
queue.append(cur.left) # then left

return ",".join(ans) # convert list to comma-separated string

def deserialize(self, data):
if not data:
return None

data = deque(data.split(",")) # convert string back to list
root = TreeNode(int(data.popleft())) # first value is the root
queue = [(root, 0)] # track parent nodes and child positions

while data:
if not queue:
return None # should never happen unless input is corrupted

cur = data.popleft()
if cur == "n":
val = None # null node, no need to create a TreeNode
else:
val = TreeNode(int(cur)) # create a new TreeNode

parent, cnt = queue.pop() # get the parent and which child we’re setting
if cnt == 0:
parent.left = val # assign left child
else:
parent.right = val # assign right child

cnt += 1 # move to the next child
if cnt < 2:
queue.append((parent, cnt)) # if parent still needs a right child, keep it in the queue
if val:
queue.append((val, 0)) # add new node to process its children later

return root