Skip to content
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
65 changes: 65 additions & 0 deletions course_schedule.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
"""
APPROACH:
1. Build a graph where each course points to the courses that depend on it (its "children").
2. Track the indegree (number of prerequisites) for every course.
3. Start with all courses that have zero prerequisites, since those can be taken right away.
4. Process courses one by one, and each time a course is "taken", reduce the indegree of its dependents.
5. If a dependent's indegree hits zero, it becomes takeable, so add it to the queue.

PATTERN:
Topological Sort (Kahn's Algorithm using BFS)

TIME COMPLEXITY:
O(V + E) - V is numCourses, E is number of prerequisite pairs, each visited once

SPACE COMPLEXITY:
O(V + E) - graph stores all edges, plus indegree array and queue of size V

EXAMPLE:
Input:
numCourses = 4
prerequisites = [[1,0],[2,0],[3,1],[3,2]]

Output: True
Why: order 0 -> 1 -> 2 -> 3 (or 0 -> 2 -> 1 -> 3) satisfies all prerequisites, no cycle exists
"""

class Solution:
def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
graph = {} # maps each course to list of courses that depend on it

for i in range(numCourses):
graph[i] = [] # initialize empty dependents list for every course

indegree = [0] * numCourses # tracks number of prerequisites per course

for course, prereq in prerequisites:
graph[prereq].append(course) # prereq must be taken before course

for course, prereq in prerequisites:
indegree[course] = indegree[course] + 1 # count each prerequisite requirement

queue = collections.deque() # queue used for BFS (Kahn's algorithm)

# start with all courses that have no prerequisites
for i in range(numCourses):
if indegree[i] == 0:
queue.append(i)

while queue:
current_course = queue.popleft() # take this course now

# unlock dependent courses by reducing their prerequisite count
for neighbor in graph[current_course]:
indegree[neighbor] = indegree[neighbor] - 1

# if a course has no more prerequisites left, it can now be taken
if indegree[neighbor] == 0:
queue.append(neighbor)

# if any course still has prerequisites left, there was a cycle
for i in range(numCourses):
if indegree[i] != 0:
return False

return True # all courses could be taken, no cycle found
64 changes: 64 additions & 0 deletions level_order_traversal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right

"""
APPROACH:
1. If the tree is empty, return an empty list right away.
2. Use a queue to do BFS, starting with just the root node.
3. Process the tree one level at a time by tracking how many nodes are in the queue at the start of each level.
4. For each node processed, record its value and add its children (if any) to the queue for the next level.
5. Once a level is fully processed, save it as one list inside the final result.

PATTERN:
Tree BFS / Level Order Traversal

TIME COMPLEXITY:
O(n) - every node is visited and processed exactly once

SPACE COMPLEXITY:
O(n) - queue can hold up to one full level of nodes, and result stores all node values

EXAMPLE:
Input:
3
/ \
9 20
/ \
15 7

Output: [[3], [9, 20], [15, 7]]
Why: level 0 has just 3, level 1 has 9 and 20, level 2 has 15 and 7
"""

class Solution:
def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:

if not root:
return [] # empty tree has no levels to return

result = [] # stores final list of levels
queue = collections.deque() # queue used for BFS

queue.append(root) # start BFS with the root node

while queue:

level_size = len(queue) # number of nodes in current level
current_level = [] # values for this level

for i in range(level_size):
current_node = queue.popleft() # process next node in this level
current_level.append(current_node.val) # record its value

# add children to queue so they get processed in the next level
if current_node.left:
queue.append(current_node.left)
if current_node.right:
queue.append(current_node.right)

result.append(current_level) # save this fully processed level
return result # all levels collected
33 changes: 33 additions & 0 deletions right_side_view.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def rightSideView(self, root: Optional[TreeNode]) -> List[int]:

if root is None:
return []

result = []
q = collections.deque()
q.append(root)

while q:
level_size = len(q)

for i in range(level_size):

current_node = q.popleft()

if i == level_size - 1:
result.append(current_node.val)

if current_node.left:
q.append(current_node.left)

if current_node.right:
q.append(current_node.right)

return result