Graph Conceptual

Graph Toolkit and Tips

Graph Leetcode Classics

Rotting Fruit

Main Idea

Intuition

Why it’s classic:

Main Explanation

Implementation

from collections import deque

class Solution:
    def orangesRotting(self, grid: list[list[int]]) -> int:
        rows, cols = len(grid), len(grid[0])
        queue = deque()
        fresh_count = 0
        
        # preprocess: find all wrotten and fresh oranges
        for r in range(rows):
            for c in range(cols):
                if grid[r][c] == 2:
                    queue.append((r, c))
                elif grid[r][c] == 1:
                    fresh_count += 1
        
        # base case: 0 fresh oranges => 0 minutes needed to have no fresh fruit
        if fresh_count == 0:
            return 0
            
        minutes = 0
        # Multi-Sourced BFS: Starting with multiple nodes in the queue 
        # then progressing all of them at once
        while queue and fresh_count > 0:
            minutes += 1
            #process all oranges currently in the queue at the current "level"
            for i in range(len(queue)):
                r, c = queue.popleft()
                
                for dr, dc in [(0, 1), (0, -1), (1, 0), (-1, 0)]:
                    nr, nc = r + dr, c + dc
                    
                    # if neighbor is within bounds and is a fresh orange
                    if 0 <= nr < rows and 0 <= nc < cols and grid[nr][nc] == 1:
                        # rot it! to track seen fresh oranges
                        grid[nr][nc] = 2
                        fresh_count -= 1
                        queue.append((nr, nc)) # then propogate
        
        # check if fresh oranges remain
        return minutes if fresh_count == 0 else -1

Course Schedule

Main Idea

Intuition

Why it’s classic

Main Explanation

Implementation - Kahn

from collections import deque


class Solution:
    def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
        neighbors = [[] for i in range(numCourses)]
        indegree = [0] * numCourses


        for a, b in prerequisites:
            # grouping is prereq --> class that has that prereq
            neighbors[a].append(b)
            indegree[b] += 1


        # add the index of all nodes
        # deque here operates conceptually the same as a queue, popleft() operates in O(1)
        # if all nodes cycle => all have indegree[i] > 0 => they never enter queue => queue clears early
        queue = deque([i for i in range(len(indegree)) if indegree[i] == 0])


        taken = 0
        while queue:
            curr = queue.popleft()
            taken += 1
            for n in neighbors[curr]:
                indegree[n] -= 1
                # if we cycle then the nodes never enter the queue => queue clears early
                if indegree[n] == 0:
                    queue.append(n)

        return taken == numCourses

Implementation - DFS

from collections import defaultdict


def canFinish(numCourses, prerequisites):
    graph = defaultdict(list)
    for a, b in prerequisites:
        graph[b].append(a)


    visited = [0] * numCourses  # 0 = unvisited, 1 = visiting, 2 = visited


    def has_cycle(course):
        if visited[course] == 1:  # cycle detected
            return True
        if visited[course] == 2:
            return False


        visited[course] = 1
        for neighbor in graph[course]:
            if has_cycle(neighbor):
                return True
        visited[course] = 2 # mark branches of node: ‘course’ has possessing no cycle
        return False


    for i in range(numCourses):
        if has_cycle(i):
            return False


    return True

Graph Leetcode Nifty