December 28, 2024
Today I worked on a general graph problem. My initial solution was much more verbose and less readable than another coder's solution. However, I feel like during an interview I would have been able to derive the simpler solution through asking questions, such as whether any course number will exceed the numCourses variable. I also need to be more mindful of what data I need to track or else I create unnecessary classes.
1class Node:2 def __init__(self, val: int, toNodes: List['Node'] = None, fromNodes: List['Node'] = None):3 self.val = val4 self.toNodes = toNodes if toNodes is not None else []5 self.fromNodes = fromNodes if fromNodes is not None else []67class Solution:8 def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:9 if not prerequisites:10 return True1112 nodes = {}13 for after, before in prerequisites:14 if after not in nodes:15 nodes[after] = Node(after)16 afterNode = nodes[after]1718 if before not in nodes:19 nodes[before] = Node(before)20 beforeNode = nodes[before]21 beforeNode.toNodes.append(afterNode)22 afterNode.fromNodes.append(beforeNode)2324 topological_order = []25 stack = set()26 visited = set()27 has_cycle = False28 def dfs(node):29 nonlocal has_cycle30 if node in stack:31 return False32 if node in visited:33 return True3435 stack.add(node)36 print(node.val)37 for child in node.toNodes:38 if not dfs(child):39 has_cycle = True40 stack.remove(node)41 visited.add(node)42 topological_order.append(node.val)43 return True4445 for node in nodes.values():46 #if not node.fromNodes:47 dfs(node)48 if has_cycle:49 return False5051 return len(topological_order) <= numCourses
The time complexity is O(M + N) where M are the number of nodes and N are the number of edges, as each are visited once. The space complexity is the same as it grows linearly with the input size. I used a class to build a directed graph which I could use to attempt a topological sort, this sort would allow me to check if there are less courses than numCourses and also detect cycles. However, the problem states numCourses is always less than the course numbers, we could instead check each course if we may satisfy it's given prerequisites up to numCourses. By default, we can say a course doesn't have any prerequisites. Therefor, we only need to check if there is ever a cycle, which we can detect using a set that tracks which courses we've taken.
If we can go through every course up to numCourses without visiting a taken class, we can return true. The topological order is unnecessary, however, it is given that a topological order exists if we can complete the courses.
1class Solution:2 def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:3 from collections import defaultdict45 pres = defaultdict(list)6 for course, pre in prerequisites:7 pres[course].append(pre)89 taken = set()10 def dfs(course):11 if not pres[course]:12 return True1314 if course in taken:15 return False # Cycle detected1617 taken.add(course)18 for pre in pres[course]:19 if not dfs(pre):20 return False2122 pres[course] = []23 return True2425 for course in range(numCourses):26 if not dfs(course):27 return False2829 return True
LeetCode 22. Generate Parentheses
LeetCode 34. Find First and Last Position of Element in Sorted Array