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
48 changes: 48 additions & 0 deletions Problem1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Problem 1: Binary Tree Level Order Traversal (https://leetcode.com/problems/binary-tree-level-order-traversal/)
# Time Complexity: O(n), we visit every node exactly once
# Space Complexity: O(n), the queue holds at most one full level of nodes at a time
# Approach:
# 1. Use a queue to process nodes level by level (left to right)
# 2. At the start of each level, snapshot the queue size,that tells us exactly how many nodes belong to THIS level
# 3. Process only those nodes, collect their values add their children to the queue for the NEXT level

# 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 levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:

# if the tree is empty, nothing to traverse, return empty list
if root is None:
return []

result = [] # result will store our final answer: a list of lists, one per level
queue = [root] # seed the queue with just the root node to kick off the traversal

while queue: # keep going as long as there are nodes left to process

# freeze the current queue length,this tells us how many nodes are on THIS level, before we start adding next-level children
size = len(queue)
temporary = [] # fresh list to collect values for just this level

# loop exactly 'size' times so we only process THIS level's nodes
for i in range(size):

# remove the front node (oldest one added), FIFO order keeps us left-to-right
current = queue.pop(0)
temporary.append(current.val) # save this node's value into the current level's list

# if a left child exists, add it to the queue, it belongs to the NEXT level
if current.left:
queue.append(current.left)

# if a right child exists, add it to the queue, also belongs to the NEXT level
if current.right:
queue.append(current.right)
result.append(temporary) # done processing this level,save it into our final result

return result # queue is empty, all levels processed, return the level-by-level result
80 changes: 80 additions & 0 deletions Problem2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Problem 2: Course Schedule (https://leetcode.com/problems/course-schedule/)
# Time Complexity: O(V + E), V = numCourses, E = number of prerequisite pairs
# We visit every course once and look at every prerequisite edge once
# Building the graph = O(E), scanning all courses = O(V), BFS loop = O(V + E)

# Space Complexity: O(V + E)
# indegrees array = O(V), graph dictionary stores all edges = O(E)
# queue holds at most all courses at once = O(V)

# Approach:
# We model courses as nodes and prerequisites as directed edges in a graph.
# A course can only be "taken" when all its prerequisites are done (indegree = 0).
# We use BFS: start with all courses that need nothing,"take" them one by one, and unlock courses that were waiting on them.
# If we successfully take all courses, no cycle exists, return True.
# If we get stuck before taking all courses, a cycle exists, return False.

class Solution:
def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:

# indegrees[i] = how many prerequisites course i still needs before it can be taken
# start everyone at 0, we will increment as we read the prerequisite pairs
indegrees = [0] * numCourses

# graph[i] = list of courses that get "unlocked" (one step closer) once course i is finished
graph = {}

# read every prerequisite pair and build our two structures
for pr in prerequisites:
# pr[0] is the course we want to take, pr[1] is the course we must finish first
# so pr[0] gains one more prerequisite it needs
indegrees[pr[0]] += 1

# and finishing pr[1] will help unlock pr[0], so add pr[0] to pr[1]'s unlock list
if pr[1] not in graph:
graph[pr[1]] = [] # first time seeing pr[1] as a key, create its list
graph[pr[1]].append(pr[0]) # pr[1] unlocks pr[0] when finished

count = 0 # count = how many courses we have successfully "taken" so far

# queue holds all courses that are currently ready to be taken (indegree = 0)
# we use deque because removing from the front (popleft) is O(1), unlike a regular list
queue = deque()

# scan all courses, any course that needs zero prerequisites can be taken right now
for i in range(numCourses):
if indegrees[i] == 0:
queue.append(i) # this course is ready immediately, add to queue
count += 1 # we are counting it as taken

# if the queue is empty here, every single course needs at least one prerequisite
# that means everyone is waiting on someone else, a cycle must exist from the start
if not queue:
return False

# if every course already had indegree 0 (no prerequisites at all), we are trivially done
if count == numCourses:
return True

# BFS: keep taking courses from the queue and unlocking their dependents
while queue:
current = queue.popleft() # take the next available course

# find all courses that were waiting on 'current' to be finished
dependencies = graph.get(current) # returns None if current unlocks nothing

if dependencies:
for dep in dependencies:
indegrees[dep] -= 1 # current is done, so dep needs one fewer prerequisite now

if indegrees[dep] == 0: # dep has no more prerequisites left, it's ready!
queue.append(dep) # add dep to the queue so we take it next
count += 1 # count it as taken

if count == numCourses: # we've taken every course, no cycle, we are done
return True

# queue ran out but we didn't take all courses
# some courses were stuck waiting forever (their indegree never reached 0)
# that means those courses are part of a cycle, impossible to finish
return False