Blog

  • LeetCode 212: Word Search II — Python Solution

    LeetCode 212: Word Search II is a Hard trie problem. This Python walkthrough develops a trie-guided board DFS solution, ties every code decision to a concrete invariant, and includes the regression check used before publication.

    This independent guide paraphrases the task rather than reproducing LeetCode’s prompt. Use the official page for the exact statement, examples, constraints, and submission runner.

    DifficultyHard
    TopicTrie
    Reusable patterntrie-guided board DFS
    ComplexityO(mn·4^L) worst-case time with strong trie pruning and O(total word characters) space

    Recognizing the pattern

    A trie shares storage and work across common prefixes, allowing a search to abandon an entire family of words after one failed edge.

    For this problem specifically, build a trie of target words and DFS from each board cell, pruning paths absent from the trie and removing words after discovery to avoid duplicates. The invariant worth writing beside the code is: Every DFS path matches the trie prefix represented by its current node and uses each board cell at most once.

    Step-by-step algorithm

    1. Identify the input state consumed by findWords(board, words) and initialize the data required by the trie-guided board DFS pattern.
    2. Build a trie of target words and DFS from each board cell, pruning paths absent from the trie and removing words after discovery to avoid duplicates.
    3. After each update, verify the page’s central invariant: Every DFS path matches the trie prefix represented by its current node and uses each board cell at most once.
    4. Finish only after the boundary behavior is covered: The same word should be emitted once; restore board cells after backtracking.

    Python solution

    class Solution:
        def findWords(self, board, words):
            root = {}
            for word in words:
                node = root
                for char in word: node = node.setdefault(char, {})
                node["$"] = word
            rows, cols, answer = len(board), len(board[0]), []
            def search(r, c, parent):
                char = board[r][c]
                if char not in parent: return
                node = parent[char]
                word = node.pop("$", None)
                if word: answer.append(word)
                board[r][c] = "#"
                for dr, dc in ((1,0),(-1,0),(0,1),(0,-1)):
                    nr, nc = r + dr, c + dc
                    if 0 <= nr < rows and 0 <= nc < cols and board[nr][nc] != "#": search(nr, nc, node)
                board[r][c] = char
                if not node: parent.pop(char)
            for r in range(rows):
                for c in range(cols): search(r, c, root)
            return answer

    Reading the implementation

    The main entry point is findWords(board, words). The named working state includes root, node, rows, char, word; those variables make the trie-guided board DFS state visible instead of hiding it in incidental control flow.

    The implementation uses 5 loops with separate responsibilities, so preprocessing and the main traversal can each stay linear in their own input. In concrete terms, build a trie of target words and DFS from each board cell, pruning paths absent from the trie and removing words after discovery to avoid duplicates.

    Correctness argument

    Initialization. The data structure starts with exactly the information known before any input element is processed.

    Preservation. Build a trie of target words and DFS from each board cell, pruning paths absent from the trie and removing words after discovery to avoid duplicates. Each update records the current item without invalidating earlier decisions; consequently, every DFS path matches the trie prefix represented by its current node and uses each board cell at most once.

    Termination. The traversal consumes a finite input or finite state space. At the end, the invariant covers the complete input, which is precisely the condition required for the returned result.

    Complexity and trade-offs

    O(mn·4^L) worst-case time with strong trie pruning and O(total word characters) space. The auxiliary-space figure excludes the returned output unless the output itself is the structure being built.

    A hash set is simpler for exact words, but it cannot answer prefix or wildcard traversal without examining many candidates. That comparison is useful in an interview because it explains why the final implementation is preferable, not merely that it passes.

    Regression check

    The published implementation belongs to a 100-problem suite that is compiled and exercised behaviorally before deployment.

    One reference assertion from that suite is shown below. It targets the normal path while the edge conditions in the next section cover the failure-prone boundaries.

    words=Solution().findWords([["o","a","a","n"],["e","t","a","e"],["i","h","k","r"],["i","f","l","v"]],["oath","pea","eat","rain"]); assert sorted(words)==["eat","oath"]

    Common mistakes and edge cases

    • Problem-specific boundary: The same word should be emitted once; restore board cells after backtracking.
    • Pattern-level pitfall: A prefix node is not necessarily a complete word; terminal markers and wildcard branching need separate handling.
    • Invariant check: after every update, confirm that every DFS path matches the trie prefix represented by its current node and uses each board cell at most once.

    Interview review checklist

    • Explain why trie-guided board DFS matches the structure of this input.
    • State the invariant in one sentence before tracing code: Every DFS path matches the trie prefix represented by its current node and uses each board cell at most once.
    • Derive O(mn·4^L) worst-case time with strong trie pruning and O(total word characters) space from how many times each element or state is visited.
    • Test the boundary explicitly: The same word should be emitted once; restore board cells after backtracking.

    Browse the searchable 100 LeetCode Python Solutions hub. Previous: 211. Design Add and Search Words Data Structure

  • LeetCode 211: Design Add and Search Words Data Structure — Python Solution

    LeetCode 211: Design Add and Search Words Data Structure is a Medium trie problem. This Python walkthrough develops a trie plus wildcard DFS solution, ties every code decision to a concrete invariant, and includes the regression check used before publication.

    This independent guide paraphrases the task rather than reproducing LeetCode’s prompt. Use the official page for the exact statement, examples, constraints, and submission runner.

    DifficultyMedium
    TopicTrie
    Reusable patterntrie plus wildcard DFS
    ComplexityO(L) insert and O(branching^wildcards) worst-case search time

    Recognizing the pattern

    A trie shares storage and work across common prefixes, allowing a search to abandon an entire family of words after one failed edge.

    For this problem specifically, insert normally. During search, a dot branches to every child while a letter follows one matching edge. The invariant worth writing beside the code is: The DFS states represent all trie nodes consistent with the processed pattern prefix.

    Step-by-step algorithm

    1. Identify the input state consumed by addWord(word) and initialize the data required by the trie plus wildcard DFS pattern.
    2. Insert normally. During search, a dot branches to every child while a letter follows one matching edge.
    3. After each update, verify the page’s central invariant: The DFS states represent all trie nodes consistent with the processed pattern prefix.
    4. Finish only after the boundary behavior is covered: A wildcard matches exactly one character, and a match must end at a terminal node.

    Python solution

    class WordDictionary:
        def __init__(self):
            self.root = {}
    
        def addWord(self, word):
            node = self.root
            for char in word: node = node.setdefault(char, {})
            node["$"] = True
    
        def search(self, word):
            def match(index, node):
                if index == len(word): return "$" in node
                char = word[index]
                if char == ".":
                    return any(key != "$" and match(index + 1, child) for key, child in node.items())
                return char in node and match(index + 1, node[char])
            return match(0, self.root)

    Reading the implementation

    The main entry point is addWord(word). The named working state includes node, char; those variables make the trie plus wildcard DFS state visible instead of hiding it in incidental control flow.

    A single main loop advances the algorithm, which is the key reason the traversal does not revisit already resolved input unnecessarily. Early returns stop as soon as the answer is forced, avoiding work that cannot change the result. In concrete terms, insert normally. During search, a dot branches to every child while a letter follows one matching edge.

    Correctness argument

    Initialization. The data structure starts with exactly the information known before any input element is processed.

    Preservation. Insert normally. During search, a dot branches to every child while a letter follows one matching edge. Each update records the current item without invalidating earlier decisions; consequently, the DFS states represent all trie nodes consistent with the processed pattern prefix.

    Termination. The traversal consumes a finite input or finite state space. At the end, the invariant covers the complete input, which is precisely the condition required for the returned result.

    Complexity and trade-offs

    O(L) insert and O(branching^wildcards) worst-case search time. The auxiliary-space figure excludes the returned output unless the output itself is the structure being built.

    A hash set is simpler for exact words, but it cannot answer prefix or wildcard traversal without examining many candidates. That comparison is useful in an interview because it explains why the final implementation is preferable, not merely that it passes.

    Regression check

    The published implementation belongs to a 100-problem suite that is compiled and exercised behaviorally before deployment.

    One reference assertion from that suite is shown below. It targets the normal path while the edge conditions in the next section cover the failure-prone boundaries.

    wd=WordDictionary(); [wd.addWord(word) for word in ["bad","dad","mad"]]; assert wd.search(".ad") and not wd.search("pad")

    Common mistakes and edge cases

    • Problem-specific boundary: A wildcard matches exactly one character, and a match must end at a terminal node.
    • Pattern-level pitfall: A prefix node is not necessarily a complete word; terminal markers and wildcard branching need separate handling.
    • Invariant check: after every update, confirm that the DFS states represent all trie nodes consistent with the processed pattern prefix.

    Interview review checklist

    • Explain why trie plus wildcard DFS matches the structure of this input.
    • State the invariant in one sentence before tracing code: The DFS states represent all trie nodes consistent with the processed pattern prefix.
    • Derive O(L) insert and O(branching^wildcards) worst-case search time from how many times each element or state is visited.
    • Test the boundary explicitly: A wildcard matches exactly one character, and a match must end at a terminal node.

    Browse the searchable 100 LeetCode Python Solutions hub. Previous: 208. Implement Trie (Prefix Tree) · Next: 212. Word Search II

  • LeetCode 208: Implement Trie (Prefix Tree) — Python Solution

    LeetCode 208: Implement Trie (Prefix Tree) is a Medium trie problem. This Python walkthrough develops a nested child maps solution, ties every code decision to a concrete invariant, and includes the regression check used before publication.

    This independent guide paraphrases the task rather than reproducing LeetCode’s prompt. Use the official page for the exact statement, examples, constraints, and submission runner.

    DifficultyMedium
    TopicTrie
    Reusable patternnested child maps
    ComplexityO(L) time per operation and O(total inserted characters) space

    Recognizing the pattern

    A trie shares storage and work across common prefixes, allowing a search to abandon an entire family of words after one failed edge.

    For this problem specifically, each character follows or creates a child node, and a terminal flag distinguishes complete words from prefixes. The invariant worth writing beside the code is: The node reached after a prefix represents exactly that character sequence.

    Step-by-step algorithm

    1. Identify the input state consumed by insert(word) and initialize the data required by the nested child maps pattern.
    2. Each character follows or creates a child node, and a terminal flag distinguishes complete words from prefixes.
    3. After each update, verify the page’s central invariant: The node reached after a prefix represents exactly that character sequence.
    4. Finish only after the boundary behavior is covered: An inserted word can also be a prefix of a longer word.

    Python solution

    class Trie:
        def __init__(self):
            self.root = {}
    
        def insert(self, word):
            node = self.root
            for char in word: node = node.setdefault(char, {})
            node["$"] = True
    
        def search(self, word):
            node = self._find(word)
            return node is not None and "$" in node
    
        def startsWith(self, prefix):
            return self._find(prefix) is not None
    
        def _find(self, text):
            node = self.root
            for char in text:
                if char not in node: return None
                node = node[char]
            return node

    Reading the implementation

    The main entry point is insert(word). The named working state includes node; those variables make the nested child maps state visible instead of hiding it in incidental control flow.

    The implementation uses 2 loops with separate responsibilities, so preprocessing and the main traversal can each stay linear in their own input. Early returns stop as soon as the answer is forced, avoiding work that cannot change the result. In concrete terms, each character follows or creates a child node, and a terminal flag distinguishes complete words from prefixes.

    Correctness argument

    Initialization. The data structure starts with exactly the information known before any input element is processed.

    Preservation. Each character follows or creates a child node, and a terminal flag distinguishes complete words from prefixes. Each update records the current item without invalidating earlier decisions; consequently, the node reached after a prefix represents exactly that character sequence.

    Termination. The traversal consumes a finite input or finite state space. At the end, the invariant covers the complete input, which is precisely the condition required for the returned result.

    Complexity and trade-offs

    O(L) time per operation and O(total inserted characters) space. The auxiliary-space figure excludes the returned output unless the output itself is the structure being built.

    A hash set is simpler for exact words, but it cannot answer prefix or wildcard traversal without examining many candidates. That comparison is useful in an interview because it explains why the final implementation is preferable, not merely that it passes.

    Regression check

    The published implementation belongs to a 100-problem suite that is compiled and exercised behaviorally before deployment.

    One reference assertion from that suite is shown below. It targets the normal path while the edge conditions in the next section cover the failure-prone boundaries.

    trie=Trie(); trie.insert("apple"); assert trie.search("apple") and trie.startsWith("app") and not trie.search("app")

    Common mistakes and edge cases

    • Problem-specific boundary: An inserted word can also be a prefix of a longer word.
    • Pattern-level pitfall: A prefix node is not necessarily a complete word; terminal markers and wildcard branching need separate handling.
    • Invariant check: after every update, confirm that the node reached after a prefix represents exactly that character sequence.

    Interview review checklist

    • Explain why nested child maps matches the structure of this input.
    • State the invariant in one sentence before tracing code: The node reached after a prefix represents exactly that character sequence.
    • Derive O(L) time per operation and O(total inserted characters) space from how many times each element or state is visited.
    • Test the boundary explicitly: An inserted word can also be a prefix of a longer word.

    Browse the searchable 100 LeetCode Python Solutions hub. Previous: 127. Word Ladder · Next: 211. Design Add and Search Words Data Structure

  • LeetCode 127: Word Ladder — Python Solution

    LeetCode 127: Word Ladder is a Hard graph bfs problem. This Python walkthrough develops a wildcard-neighbor BFS solution, ties every code decision to a concrete invariant, and includes the regression check used before publication.

    This independent guide paraphrases the task rather than reproducing LeetCode’s prompt. Use the official page for the exact statement, examples, constraints, and submission runner.

    DifficultyHard
    TopicGraph BFS
    Reusable patternwildcard-neighbor BFS
    ComplexityO(NL^2) preprocessing and traversal time and O(NL) space

    Recognizing the pattern

    BFS explores an unweighted state graph in increasing move count, so the first arrival gives a shortest transformation.

    For this problem specifically, index words by wildcard patterns, then BFS from the beginning word through words sharing a pattern. The invariant worth writing beside the code is: The first BFS layer reaching a word uses the shortest transformation sequence.

    Step-by-step algorithm

    1. Identify the input state consumed by ladderLength(beginWord, endWord, wordList) and initialize the data required by the wildcard-neighbor BFS pattern.
    2. Index words by wildcard patterns, then BFS from the beginning word through words sharing a pattern.
    3. After each update, verify the page’s central invariant: The first BFS layer reaching a word uses the shortest transformation sequence.
    4. Finish only after the boundary behavior is covered: The end word must be in the dictionary; each transformation changes exactly one character.

    Python solution

    from collections import defaultdict, deque
    
    class Solution:
        def ladderLength(self, beginWord, endWord, wordList):
            if endWord not in wordList: return 0
            patterns = defaultdict(list)
            width = len(beginWord)
            for word in wordList:
                for i in range(width): patterns[word[:i] + "*" + word[i + 1:]].append(word)
            queue, seen = deque([(beginWord, 1)]), {beginWord}
            while queue:
                word, distance = queue.popleft()
                for i in range(width):
                    pattern = word[:i] + "*" + word[i + 1:]
                    for neighbor in patterns[pattern]:
                        if neighbor == endWord: return distance + 1
                        if neighbor not in seen:
                            seen.add(neighbor); queue.append((neighbor, distance + 1))
                    patterns[pattern] = []
            return 0

    Reading the implementation

    The main entry point is ladderLength(beginWord, endWord, wordList). The named working state includes patterns, width, queue, word, pattern; those variables make the wildcard-neighbor BFS state visible instead of hiding it in incidental control flow.

    The implementation uses 5 loops with separate responsibilities, so preprocessing and the main traversal can each stay linear in their own input. In concrete terms, index words by wildcard patterns, then BFS from the beginning word through words sharing a pattern.

    Correctness argument

    Initialization. The starting state is the only item in the first frontier, so its distance or level is exact.

    Preservation. Index words by wildcard patterns, then BFS from the beginning word through words sharing a pattern. Because the queue processes earlier layers first, the first BFS layer reaching a word uses the shortest transformation sequence.

    Termination. Each state is accepted at most once. The first accepted goal therefore has the minimum possible layer, or exhausting the queue proves no valid path exists.

    Complexity and trade-offs

    O(NL^2) preprocessing and traversal time and O(NL) space. The auxiliary-space figure excludes the returned output unless the output itself is the structure being built.

    DFS can determine reachability but cannot stop at the first found path when a minimum number of moves is required. That comparison is useful in an interview because it explains why the final implementation is preferable, not merely that it passes.

    Regression check

    The published implementation belongs to a 100-problem suite that is compiled and exercised behaviorally before deployment.

    One reference assertion from that suite is shown below. It targets the normal path while the edge conditions in the next section cover the failure-prone boundaries.

    assert Solution().ladderLength("hit","cog",["hot","dot","dog","lot","log","cog"])==5

    Common mistakes and edge cases

    • Problem-specific boundary: The end word must be in the dictionary; each transformation changes exactly one character.
    • Pattern-level pitfall: Generate only legal neighbors, mark them before enqueueing, and count layers consistently with the problem’s definition of a move.
    • Invariant check: after every update, confirm that the first BFS layer reaching a word uses the shortest transformation sequence.

    Interview review checklist

    • Explain why wildcard-neighbor BFS matches the structure of this input.
    • State the invariant in one sentence before tracing code: The first BFS layer reaching a word uses the shortest transformation sequence.
    • Derive O(NL^2) preprocessing and traversal time and O(NL) space from how many times each element or state is visited.
    • Test the boundary explicitly: The end word must be in the dictionary; each transformation changes exactly one character.

    Browse the searchable 100 LeetCode Python Solutions hub. Previous: 433. Minimum Genetic Mutation · Next: 208. Implement Trie (Prefix Tree)

  • LeetCode 433: Minimum Genetic Mutation — Python Solution

    LeetCode 433: Minimum Genetic Mutation is a Medium graph bfs problem. This Python walkthrough develops a single-character BFS solution, ties every code decision to a concrete invariant, and includes the regression check used before publication.

    This independent guide paraphrases the task rather than reproducing LeetCode’s prompt. Use the official page for the exact statement, examples, constraints, and submission runner.

    DifficultyMedium
    TopicGraph BFS
    Reusable patternsingle-character BFS
    ComplexityO(BL) practical time and O(B) space

    Recognizing the pattern

    BFS explores an unweighted state graph in increasing move count, so the first arrival gives a shortest transformation.

    For this problem specifically, bFS through valid bank strings formed by replacing one position with A, C, G, or T. The invariant worth writing beside the code is: Every queued gene is valid and reached in the minimum number of mutations.

    Step-by-step algorithm

    1. Identify the input state consumed by minMutation(startGene, endGene, bank) and initialize the data required by the single-character BFS pattern.
    2. BFS through valid bank strings formed by replacing one position with A, C, G, or T.
    3. After each update, verify the page’s central invariant: Every queued gene is valid and reached in the minimum number of mutations.
    4. Finish only after the boundary behavior is covered: If the end gene is absent from the bank it is unreachable unless it already equals the start.

    Python solution

    from collections import deque
    
    class Solution:
        def minMutation(self, startGene, endGene, bank):
            if startGene == endGene: return 0
            allowed = set(bank)
            if endGene not in allowed: return -1
            queue = deque([(startGene, 0)]); seen = {startGene}
            for_queue = "ACGT"
            while queue:
                gene, steps = queue.popleft()
                for i in range(len(gene)):
                    for base in for_queue:
                        candidate = gene[:i] + base + gene[i + 1:]
                        if candidate == endGene: return steps + 1
                        if candidate in allowed and candidate not in seen:
                            seen.add(candidate); queue.append((candidate, steps + 1))
            return -1

    Reading the implementation

    The main entry point is minMutation(startGene, endGene, bank). The named working state includes allowed, queue, for_queue, gene, candidate; those variables make the single-character BFS state visible instead of hiding it in incidental control flow.

    The implementation uses 3 loops with separate responsibilities, so preprocessing and the main traversal can each stay linear in their own input. In concrete terms, bFS through valid bank strings formed by replacing one position with A, C, G, or T.

    Correctness argument

    Initialization. The starting state is the only item in the first frontier, so its distance or level is exact.

    Preservation. BFS through valid bank strings formed by replacing one position with A, C, G, or T. Because the queue processes earlier layers first, every queued gene is valid and reached in the minimum number of mutations.

    Termination. Each state is accepted at most once. The first accepted goal therefore has the minimum possible layer, or exhausting the queue proves no valid path exists.

    Complexity and trade-offs

    O(BL) practical time and O(B) space. The auxiliary-space figure excludes the returned output unless the output itself is the structure being built.

    DFS can determine reachability but cannot stop at the first found path when a minimum number of moves is required. That comparison is useful in an interview because it explains why the final implementation is preferable, not merely that it passes.

    Regression check

    The published implementation belongs to a 100-problem suite that is compiled and exercised behaviorally before deployment.

    One reference assertion from that suite is shown below. It targets the normal path while the edge conditions in the next section cover the failure-prone boundaries.

    assert Solution().minMutation("AACCGGTT","AACCGGTA",["AACCGGTA"])==1

    Common mistakes and edge cases

    • Problem-specific boundary: If the end gene is absent from the bank it is unreachable unless it already equals the start.
    • Pattern-level pitfall: Generate only legal neighbors, mark them before enqueueing, and count layers consistently with the problem’s definition of a move.
    • Invariant check: after every update, confirm that every queued gene is valid and reached in the minimum number of mutations.

    Interview review checklist

    • Explain why single-character BFS matches the structure of this input.
    • State the invariant in one sentence before tracing code: Every queued gene is valid and reached in the minimum number of mutations.
    • Derive O(BL) practical time and O(B) space from how many times each element or state is visited.
    • Test the boundary explicitly: If the end gene is absent from the bank it is unreachable unless it already equals the start.

    Browse the searchable 100 LeetCode Python Solutions hub. Previous: 909. Snakes and Ladders · Next: 127. Word Ladder

  • LeetCode 909: Snakes and Ladders — Python Solution

    LeetCode 909: Snakes and Ladders is a Medium graph bfs problem. This Python walkthrough develops a board-index BFS solution, ties every code decision to a concrete invariant, and includes the regression check used before publication.

    This independent guide paraphrases the task rather than reproducing LeetCode’s prompt. Use the official page for the exact statement, examples, constraints, and submission runner.

    DifficultyMedium
    TopicGraph BFS
    Reusable patternboard-index BFS
    ComplexityO(n^2) time and O(n^2) space

    Recognizing the pattern

    BFS explores an unweighted state graph in increasing move count, so the first arrival gives a shortest transformation.

    For this problem specifically, map square numbers to boustrophedon coordinates and BFS over dice results, applying at most one snake or ladder per move. The invariant worth writing beside the code is: The first time BFS reaches a square uses the minimum number of dice throws.

    Step-by-step algorithm

    1. Identify the input state consumed by snakesAndLadders(board) and initialize the data required by the board-index BFS pattern.
    2. Map square numbers to boustrophedon coordinates and BFS over dice results, applying at most one snake or ladder per move.
    3. After each update, verify the page’s central invariant: The first time BFS reaches a square uses the minimum number of dice throws.
    4. Finish only after the boundary behavior is covered: Do not chain a second jump in the same move; row direction alternates from the bottom.

    Python solution

    from collections import deque
    
    class Solution:
        def snakesAndLadders(self, board):
            n = len(board)
            def coordinates(square):
                row_from_bottom, offset = divmod(square - 1, n)
                row = n - 1 - row_from_bottom
                col = offset if row_from_bottom % 2 == 0 else n - 1 - offset
                return row, col
            queue, seen = deque([(1, 0)]), {1}
            while queue:
                square, moves = queue.popleft()
                if square == n * n: return moves
                for rolled in range(square + 1, min(square + 6, n * n) + 1):
                    r, c = coordinates(rolled)
                    destination = board[r][c] if board[r][c] != -1 else rolled
                    if destination not in seen:
                        seen.add(destination); queue.append((destination, moves + 1))
            return -1

    Reading the implementation

    The main entry point is snakesAndLadders(board). The named working state includes n, row_from_bottom, row, col, queue, square; those variables make the board-index BFS state visible instead of hiding it in incidental control flow.

    The implementation uses 2 loops with separate responsibilities, so preprocessing and the main traversal can each stay linear in their own input. Early returns stop as soon as the answer is forced, avoiding work that cannot change the result. In concrete terms, map square numbers to boustrophedon coordinates and BFS over dice results, applying at most one snake or ladder per move.

    Correctness argument

    Initialization. The starting state is the only item in the first frontier, so its distance or level is exact.

    Preservation. Map square numbers to boustrophedon coordinates and BFS over dice results, applying at most one snake or ladder per move. Because the queue processes earlier layers first, the first time BFS reaches a square uses the minimum number of dice throws.

    Termination. Each state is accepted at most once. The first accepted goal therefore has the minimum possible layer, or exhausting the queue proves no valid path exists.

    Complexity and trade-offs

    O(n^2) time and O(n^2) space. The auxiliary-space figure excludes the returned output unless the output itself is the structure being built.

    DFS can determine reachability but cannot stop at the first found path when a minimum number of moves is required. That comparison is useful in an interview because it explains why the final implementation is preferable, not merely that it passes.

    Regression check

    The published implementation belongs to a 100-problem suite that is compiled and exercised behaviorally before deployment.

    One reference assertion from that suite is shown below. It targets the normal path while the edge conditions in the next section cover the failure-prone boundaries.

    board=[[-1,-1,-1,-1,-1,-1],[-1,-1,-1,-1,-1,-1],[-1,-1,-1,-1,-1,-1],[-1,35,-1,-1,13,-1],[-1,-1,-1,-1,-1,-1],[-1,15,-1,-1,-1,-1]]; assert Solution().snakesAndLadders(board)==4

    Common mistakes and edge cases

    • Problem-specific boundary: Do not chain a second jump in the same move; row direction alternates from the bottom.
    • Pattern-level pitfall: Generate only legal neighbors, mark them before enqueueing, and count layers consistently with the problem’s definition of a move.
    • Invariant check: after every update, confirm that the first time BFS reaches a square uses the minimum number of dice throws.

    Interview review checklist

    • Explain why board-index BFS matches the structure of this input.
    • State the invariant in one sentence before tracing code: The first time BFS reaches a square uses the minimum number of dice throws.
    • Derive O(n^2) time and O(n^2) space from how many times each element or state is visited.
    • Test the boundary explicitly: Do not chain a second jump in the same move; row direction alternates from the bottom.

    Browse the searchable 100 LeetCode Python Solutions hub. Previous: 210. Course Schedule II · Next: 433. Minimum Genetic Mutation

  • LeetCode 210: Course Schedule II — Python Solution

    LeetCode 210: Course Schedule II is a Medium graph general problem. This Python walkthrough develops a topological ordering solution, ties every code decision to a concrete invariant, and includes the regression check used before publication.

    This independent guide paraphrases the task rather than reproducing LeetCode’s prompt. Use the official page for the exact statement, examples, constraints, and submission runner.

    DifficultyMedium
    TopicGraph General
    Reusable patterntopological ordering
    ComplexityO(V+E) time and O(V+E) space

    Recognizing the pattern

    Graph traversal separates reachability from representation: adjacency describes possible moves, while a visited structure prevents repeated work.

    For this problem specifically, use Kahn’s algorithm and append each removed zero-indegree course to the order; return it only if every course is processed. The invariant worth writing beside the code is: Every appended course has all prerequisites earlier in the output.

    Step-by-step algorithm

    1. Identify the input state consumed by findOrder(numCourses, prerequisites) and initialize the data required by the topological ordering pattern.
    2. Use Kahn’s algorithm and append each removed zero-indegree course to the order; return it only if every course is processed.
    3. After each update, verify the page’s central invariant: Every appended course has all prerequisites earlier in the output.
    4. Finish only after the boundary behavior is covered: A cycle returns an empty list; isolated courses begin with zero indegree.

    Python solution

    from collections import deque
    
    class Solution:
        def findOrder(self, numCourses, prerequisites):
            graph = [[] for _ in range(numCourses)]; indegree = [0] * numCourses
            for course, prerequisite in prerequisites:
                graph[prerequisite].append(course); indegree[course] += 1
            queue = deque(i for i, degree in enumerate(indegree) if degree == 0)
            order = []
            while queue:
                course = queue.popleft(); order.append(course)
                for following in graph[course]:
                    indegree[following] -= 1
                    if indegree[following] == 0: queue.append(following)
            return order if len(order) == numCourses else []

    Reading the implementation

    The main entry point is findOrder(numCourses, prerequisites). The named working state includes graph, queue, order, course; those variables make the topological ordering state visible instead of hiding it in incidental control flow.

    The implementation uses 3 loops with separate responsibilities, so preprocessing and the main traversal can each stay linear in their own input. In concrete terms, use Kahn’s algorithm and append each removed zero-indegree course to the order; return it only if every course is processed.

    Correctness argument

    Initialization. The data structure starts with exactly the information known before any input element is processed.

    Preservation. Use Kahn’s algorithm and append each removed zero-indegree course to the order; return it only if every course is processed. Each update records the current item without invalidating earlier decisions; consequently, every appended course has all prerequisites earlier in the output.

    Termination. The traversal consumes a finite input or finite state space. At the end, the invariant covers the complete input, which is precisely the condition required for the returned result.

    Complexity and trade-offs

    O(V+E) time and O(V+E) space. The auxiliary-space figure excludes the returned output unless the output itself is the structure being built.

    DFS and BFS often have the same asymptotic cost; choose DFS for recursive structure and BFS when distance or processing order matters. That comparison is useful in an interview because it explains why the final implementation is preferable, not merely that it passes.

    Regression check

    The published implementation belongs to a 100-problem suite that is compiled and exercised behaviorally before deployment.

    One reference assertion from that suite is shown below. It targets the normal path while the edge conditions in the next section cover the failure-prone boundaries.

    assert Solution().findOrder(2,[[1,0]])==[0,1]

    Common mistakes and edge cases

    • Problem-specific boundary: A cycle returns an empty list; isolated courses begin with zero indegree.
    • Pattern-level pitfall: Mark a state when it is discovered rather than after all of its neighbors are processed, or cycles can enqueue it repeatedly.
    • Invariant check: after every update, confirm that every appended course has all prerequisites earlier in the output.

    Interview review checklist

    • Explain why topological ordering matches the structure of this input.
    • State the invariant in one sentence before tracing code: Every appended course has all prerequisites earlier in the output.
    • Derive O(V+E) time and O(V+E) space from how many times each element or state is visited.
    • Test the boundary explicitly: A cycle returns an empty list; isolated courses begin with zero indegree.

    Browse the searchable 100 LeetCode Python Solutions hub. Previous: 207. Course Schedule · Next: 909. Snakes and Ladders

  • LeetCode 207: Course Schedule — Python Solution

    LeetCode 207: Course Schedule is a Medium graph general problem. This Python walkthrough develops a Kahn topological sort solution, ties every code decision to a concrete invariant, and includes the regression check used before publication.

    This independent guide paraphrases the task rather than reproducing LeetCode’s prompt. Use the official page for the exact statement, examples, constraints, and submission runner.

    DifficultyMedium
    TopicGraph General
    Reusable patternKahn topological sort
    ComplexityO(V+E) time and O(V+E) space

    Recognizing the pattern

    Graph traversal separates reachability from representation: adjacency describes possible moves, while a visited structure prevents repeated work.

    For this problem specifically, count prerequisites as indegrees, repeatedly remove zero-indegree courses, and reduce the indegrees of dependent courses. The invariant worth writing beside the code is: The queue contains exactly the currently schedulable courses with no remaining prerequisites.

    Step-by-step algorithm

    1. Identify the input state consumed by canFinish(numCourses, prerequisites) and initialize the data required by the Kahn topological sort pattern.
    2. Count prerequisites as indegrees, repeatedly remove zero-indegree courses, and reduce the indegrees of dependent courses.
    3. After each update, verify the page’s central invariant: The queue contains exactly the currently schedulable courses with no remaining prerequisites.
    4. Finish only after the boundary behavior is covered: Processing fewer than all courses proves a directed cycle.

    Python solution

    from collections import deque
    
    class Solution:
        def canFinish(self, numCourses, prerequisites):
            graph = [[] for _ in range(numCourses)]; indegree = [0] * numCourses
            for course, prerequisite in prerequisites:
                graph[prerequisite].append(course); indegree[course] += 1
            queue = deque(i for i, degree in enumerate(indegree) if degree == 0)
            completed = 0
            while queue:
                course = queue.popleft(); completed += 1
                for following in graph[course]:
                    indegree[following] -= 1
                    if indegree[following] == 0: queue.append(following)
            return completed == numCourses

    Reading the implementation

    The main entry point is canFinish(numCourses, prerequisites). The named working state includes graph, queue, completed, course; those variables make the Kahn topological sort state visible instead of hiding it in incidental control flow.

    The implementation uses 3 loops with separate responsibilities, so preprocessing and the main traversal can each stay linear in their own input. In concrete terms, count prerequisites as indegrees, repeatedly remove zero-indegree courses, and reduce the indegrees of dependent courses.

    Correctness argument

    Initialization. The data structure starts with exactly the information known before any input element is processed.

    Preservation. Count prerequisites as indegrees, repeatedly remove zero-indegree courses, and reduce the indegrees of dependent courses. Each update records the current item without invalidating earlier decisions; consequently, the queue contains exactly the currently schedulable courses with no remaining prerequisites.

    Termination. The traversal consumes a finite input or finite state space. At the end, the invariant covers the complete input, which is precisely the condition required for the returned result.

    Complexity and trade-offs

    O(V+E) time and O(V+E) space. The auxiliary-space figure excludes the returned output unless the output itself is the structure being built.

    DFS and BFS often have the same asymptotic cost; choose DFS for recursive structure and BFS when distance or processing order matters. That comparison is useful in an interview because it explains why the final implementation is preferable, not merely that it passes.

    Regression check

    The published implementation belongs to a 100-problem suite that is compiled and exercised behaviorally before deployment.

    One reference assertion from that suite is shown below. It targets the normal path while the edge conditions in the next section cover the failure-prone boundaries.

    assert Solution().canFinish(2,[[1,0]]) and not Solution().canFinish(2,[[1,0],[0,1]])

    Common mistakes and edge cases

    • Problem-specific boundary: Processing fewer than all courses proves a directed cycle.
    • Pattern-level pitfall: Mark a state when it is discovered rather than after all of its neighbors are processed, or cycles can enqueue it repeatedly.
    • Invariant check: after every update, confirm that the queue contains exactly the currently schedulable courses with no remaining prerequisites.

    Interview review checklist

    • Explain why Kahn topological sort matches the structure of this input.
    • State the invariant in one sentence before tracing code: The queue contains exactly the currently schedulable courses with no remaining prerequisites.
    • Derive O(V+E) time and O(V+E) space from how many times each element or state is visited.
    • Test the boundary explicitly: Processing fewer than all courses proves a directed cycle.

    Browse the searchable 100 LeetCode Python Solutions hub. Previous: 399. Evaluate Division · Next: 210. Course Schedule II

  • LeetCode 399: Evaluate Division — Python Solution

    LeetCode 399: Evaluate Division is a Medium graph general problem. This Python walkthrough develops a weighted graph search solution, ties every code decision to a concrete invariant, and includes the regression check used before publication.

    This independent guide paraphrases the task rather than reproducing LeetCode’s prompt. Use the official page for the exact statement, examples, constraints, and submission runner.

    DifficultyMedium
    TopicGraph General
    Reusable patternweighted graph search
    ComplexityO((V+E) per query) time and O(V+E) space

    Recognizing the pattern

    Graph traversal separates reachability from representation: adjacency describes possible moves, while a visited structure prevents repeated work.

    For this problem specifically, represent each equation as two reciprocal weighted edges. For a query, search for a path and multiply weights along it. The invariant worth writing beside the code is: The accumulated product equals the ratio from the query source to the current graph node.

    Step-by-step algorithm

    1. Identify the input state consumed by calcEquation(equations, values, queries) and initialize the data required by the weighted graph search pattern.
    2. Represent each equation as two reciprocal weighted edges. For a query, search for a path and multiply weights along it.
    3. After each update, verify the page’s central invariant: The accumulated product equals the ratio from the query source to the current graph node.
    4. Finish only after the boundary behavior is covered: Unknown variables and disconnected pairs return -1; a known variable divided by itself is one.

    Python solution

    from collections import defaultdict
    
    class Solution:
        def calcEquation(self, equations, values, queries):
            graph = defaultdict(list)
            for (a, b), value in zip(equations, values):
                graph[a].append((b, value)); graph[b].append((a, 1.0 / value))
            def search(start, end):
                if start not in graph or end not in graph: return -1.0
                stack, seen = [(start, 1.0)], {start}
                while stack:
                    node, product = stack.pop()
                    if node == end: return product
                    for neighbor, weight in graph[node]:
                        if neighbor not in seen:
                            seen.add(neighbor); stack.append((neighbor, product * weight))
                return -1.0
            return [search(a, b) for a, b in queries]

    Reading the implementation

    The main entry point is calcEquation(equations, values, queries). The named working state includes graph, stack, node; those variables make the weighted graph search state visible instead of hiding it in incidental control flow.

    The implementation uses 3 loops with separate responsibilities, so preprocessing and the main traversal can each stay linear in their own input. Early returns stop as soon as the answer is forced, avoiding work that cannot change the result. In concrete terms, represent each equation as two reciprocal weighted edges. For a query, search for a path and multiply weights along it.

    Correctness argument

    Initialization. The data structure starts with exactly the information known before any input element is processed.

    Preservation. Represent each equation as two reciprocal weighted edges. For a query, search for a path and multiply weights along it. Each update records the current item without invalidating earlier decisions; consequently, the accumulated product equals the ratio from the query source to the current graph node.

    Termination. The traversal consumes a finite input or finite state space. At the end, the invariant covers the complete input, which is precisely the condition required for the returned result.

    Complexity and trade-offs

    O((V+E) per query) time and O(V+E) space. The auxiliary-space figure excludes the returned output unless the output itself is the structure being built.

    DFS and BFS often have the same asymptotic cost; choose DFS for recursive structure and BFS when distance or processing order matters. That comparison is useful in an interview because it explains why the final implementation is preferable, not merely that it passes.

    Regression check

    The published implementation belongs to a 100-problem suite that is compiled and exercised behaviorally before deployment.

    One reference assertion from that suite is shown below. It targets the normal path while the edge conditions in the next section cover the failure-prone boundaries.

    values=Solution().calcEquation([["a","b"],["b","c"]],[2.0,3.0],[["a","c"],["b","a"],["a","e"]]); assert values==[6.0,0.5,-1.0]

    Common mistakes and edge cases

    • Problem-specific boundary: Unknown variables and disconnected pairs return -1; a known variable divided by itself is one.
    • Pattern-level pitfall: Mark a state when it is discovered rather than after all of its neighbors are processed, or cycles can enqueue it repeatedly.
    • Invariant check: after every update, confirm that the accumulated product equals the ratio from the query source to the current graph node.

    Interview review checklist

    • Explain why weighted graph search matches the structure of this input.
    • State the invariant in one sentence before tracing code: The accumulated product equals the ratio from the query source to the current graph node.
    • Derive O((V+E) per query) time and O(V+E) space from how many times each element or state is visited.
    • Test the boundary explicitly: Unknown variables and disconnected pairs return -1; a known variable divided by itself is one.

    Browse the searchable 100 LeetCode Python Solutions hub. Previous: 133. Clone Graph · Next: 207. Course Schedule

  • LeetCode 133: Clone Graph — Python Solution

    LeetCode 133: Clone Graph is a Medium graph general problem. This Python walkthrough develops a DFS memoized cloning solution, ties every code decision to a concrete invariant, and includes the regression check used before publication.

    This independent guide paraphrases the task rather than reproducing LeetCode’s prompt. Use the official page for the exact statement, examples, constraints, and submission runner.

    DifficultyMedium
    TopicGraph General
    Reusable patternDFS memoized cloning
    ComplexityO(V+E) time and O(V) space

    Recognizing the pattern

    Graph traversal separates reachability from representation: adjacency describes possible moves, while a visited structure prevents repeated work.

    For this problem specifically, create and memoize a clone before recursively cloning neighbors, then attach cloned neighbor references. The invariant worth writing beside the code is: The map contains the unique clone for every discovered original node, preventing infinite recursion and duplicate clones.

    Step-by-step algorithm

    1. Identify the input state consumed by cloneGraph(node) and initialize the data required by the DFS memoized cloning pattern.
    2. Create and memoize a clone before recursively cloning neighbors, then attach cloned neighbor references.
    3. After each update, verify the page’s central invariant: The map contains the unique clone for every discovered original node, preventing infinite recursion and duplicate clones.
    4. Finish only after the boundary behavior is covered: The graph may contain cycles, self-loops, or a single node.

    Python solution

    LeetCode supplies the list, tree, or graph node class referenced by this method. The downloadable test suite includes compatible local node definitions so the implementation can also run outside the judge.

    class Solution:
        def cloneGraph(self, node):
            copies = {}
            def clone(original):
                if not original: return None
                if original in copies: return copies[original]
                copy = Node(original.val)
                copies[original] = copy
                copy.neighbors = [clone(neighbor) for neighbor in original.neighbors]
                return copy
            return clone(node)

    Reading the implementation

    The main entry point is cloneGraph(node). The named working state includes copies, copy; those variables make the DFS memoized cloning state visible instead of hiding it in incidental control flow.

    The method expresses the transformation directly without a general traversal loop. Early returns stop as soon as the answer is forced, avoiding work that cannot change the result. In concrete terms, create and memoize a clone before recursively cloning neighbors, then attach cloned neighbor references.

    Correctness argument

    Initialization. The data structure starts with exactly the information known before any input element is processed.

    Preservation. Create and memoize a clone before recursively cloning neighbors, then attach cloned neighbor references. Each update records the current item without invalidating earlier decisions; consequently, the map contains the unique clone for every discovered original node, preventing infinite recursion and duplicate clones.

    Termination. The traversal consumes a finite input or finite state space. At the end, the invariant covers the complete input, which is precisely the condition required for the returned result.

    Complexity and trade-offs

    O(V+E) time and O(V) space. The auxiliary-space figure excludes the returned output unless the output itself is the structure being built.

    DFS and BFS often have the same asymptotic cost; choose DFS for recursive structure and BFS when distance or processing order matters. That comparison is useful in an interview because it explains why the final implementation is preferable, not merely that it passes.

    Regression check

    The published implementation belongs to a 100-problem suite that is compiled and exercised behaviorally before deployment.

    One reference assertion from that suite is shown below. It targets the normal path while the edge conditions in the next section cover the failure-prone boundaries.

    g1,g2=Node(1),Node(2); g1.neighbors=[g2]; g2.neighbors=[g1]; clone=Solution().cloneGraph(g1); assert clone is not g1 and clone.neighbors[0].neighbors[0] is clone

    Common mistakes and edge cases

    • Problem-specific boundary: The graph may contain cycles, self-loops, or a single node.
    • Pattern-level pitfall: Mark a state when it is discovered rather than after all of its neighbors are processed, or cycles can enqueue it repeatedly.
    • Invariant check: after every update, confirm that the map contains the unique clone for every discovered original node, preventing infinite recursion and duplicate clones.

    Interview review checklist

    • Explain why DFS memoized cloning matches the structure of this input.
    • State the invariant in one sentence before tracing code: The map contains the unique clone for every discovered original node, preventing infinite recursion and duplicate clones.
    • Derive O(V+E) time and O(V) space from how many times each element or state is visited.
    • Test the boundary explicitly: The graph may contain cycles, self-loops, or a single node.

    Browse the searchable 100 LeetCode Python Solutions hub. Previous: 130. Surrounded Regions · Next: 399. Evaluate Division