AlgoAtlas

Backtracking

Phase 3 22 solutions

Backtracking

mindmap
  root((Backtracking))
    Subsets
      Sum of All Subsets XOR Total LC1863
      Subsets LC78
      Subsets II LC90
    Combinations
      Combination Sum LC39
      Combination Sum II LC40
      Combination Sum III LC216
      Combinations LC77
      Letter Combinations of Phone Number LC17
    Permutations
      Permutations LC46
      Permutations II LC47
    Grid Search
      Word Search LC79
      Rat in a Maze
      Sudoku Solver LC37
    Constraint Satisfaction
      N Queens LC51
      N Queens II LC52
      Generate Parentheses LC22
      M Coloring Problem
    Partitioning
      Palindrome Partitioning LC131
      Matchsticks to Square LC473
      Partition to K Equal Sum Subsets LC698
      Word Break II LC140
    Expression Building
      Expression Add Operators LC282

Problem List

# Problem LC Difficulty
1 Sum of All Subsets XOR Total 1863 ๐ŸŸข Easy
2 Subsets 78 ๐ŸŸก Medium
3 Combination Sum 39 ๐ŸŸก Medium
4 Combination Sum II 40 ๐ŸŸก Medium
5 Combinations 77 ๐ŸŸก Medium
6 Permutations 46 ๐ŸŸก Medium
7 Subsets II 90 ๐ŸŸก Medium
8 Permutations II 47 ๐ŸŸก Medium
9 Generate Parentheses 22 ๐ŸŸก Medium
10 Word Search 79 ๐ŸŸก Medium
11 Palindrome Partitioning 131 ๐ŸŸก Medium
12 Letter Combinations of Phone Number 17 ๐ŸŸก Medium
13 Matchsticks to Square 473 ๐ŸŸก Medium
14 Partition to K Equal Sum Subsets 698 ๐ŸŸก Medium
15 N Queens 51 ๐Ÿ”ด Hard
16 N Queens II 52 ๐Ÿ”ด Hard
17 Word Break II 140 ๐Ÿ”ด Hard
18 Combination Sum III 216 ๐ŸŸก Medium
19 Rat in a Maze โ€” ๐ŸŸก Medium
20 M Coloring Problem โ€” ๐ŸŸก Medium
21 Sudoku Solver 37 ๐Ÿ”ด Hard
22 Expression Add Operators 282 ๐Ÿ”ด Hard

Approach Diagrams

Backtracking Template

flowchart TD
    A[backtrack state] --> B{"Base case<br/>met?"}
    B -- Yes --> C["Add to results<br/>or count"]
    B -- No --> D[Loop over choices]
    D --> E["CHOOSE:<br/>add choice to state"]
    E --> F["EXPLORE:<br/>backtrack next state"]
    F --> G["UNCHOOSE:<br/>remove choice from state"]
    G --> D
    C --> H[Return]

Subsets vs Combinations vs Permutations

flowchart TD
    A[What are you building?] --> B{Order matters?}
    B -- No --> C{Repetition allowed?}
    B -- Yes --> D{Duplicates in input?}
    C -- No, pick any subset --> E["Subsets<br/>LC 78, 90<br/>start index moves forward"]
    C -- Yes, reuse elements --> F["Combination Sum<br/>LC 39<br/>start index stays"]
    C -- No, pick exactly k --> G["Combinations<br/>LC 77<br/>start index moves forward"]
    D -- No --> H["Permutations<br/>LC 46<br/>use visited array"]
    D -- Yes --> I["Permutations II<br/>LC 47<br/>sort + skip duplicates"]

Duplicate Handling Pattern

flowchart TD
    A[Input has duplicates] --> B[Sort the array first]
    B --> C[In the loop at each level]
    C --> D{"i > start AND<br/>nums i == nums i-1?"}
    D -- Yes --> E["Skip: continue<br/>avoids duplicate branches"]
    D -- No --> F[Choose nums i]
    F --> G["Recurse with i+1<br/>or i for reuse"]
    G --> H[Unchoose]
    H --> C
    E --> C
    C --> I["LC 40 Combo Sum II<br/>LC 90 Subsets II<br/>LC 47 Permutations II"]

Constraint Satisfaction โ€” N-Queens Flow

flowchart TD
    A[Place queen in row r] --> B{row == n?}
    B -- Yes --> C[Valid solution found]
    B -- No --> D[Try each column c in row r]
    D --> E{"Is c safe?<br/>check col, diag, anti-diag"}
    E -- No --> F[Try next column]
    E -- Yes --> G["Place queen at r,c<br/>mark col, diag, anti-diag"]
    G --> H[Recurse row r+1]
    H --> I["Remove queen<br/>unmark sets"]
    I --> F
    F --> D

Pruning Strategies

flowchart TD
    A[Pruning Type] --> B{When to prune?}
    B -- Sum exceeds target --> C["Combination Sum<br/>if curr_sum > target: return"]
    B -- Remaining elements<br/>not enough --> D["Combinations<br/>if n-i+1 < k-len: return"]
    B -- Partition impossible --> E["Matchsticks / K Subsets<br/>if bucket_sum > target: skip<br/>sort descending first"]
    B -- Invalid constraint --> F["N-Queens<br/>if col or diag used: skip"]
    B -- Word mismatch --> G["Word Search<br/>if board char != word char: return"]
    B -- Parentheses invalid --> H["Generate Parens<br/>if close > open: return<br/>if open > n: return"]

Complexity Summary

Problem Time Space Pattern
sum_of_all_subset_xor_totals O(2โฟ) O(n) Include/exclude
subsets O(nยท2โฟ) O(n) Include/exclude
combination_sum O(2^(t/min)) O(t/min) Unbounded pick
combination_sum_ii O(2โฟ) O(n) Sort + skip dups
combinations O(kยทC(n,k)) O(k) Fixed-size pick
permutations O(nยทn!) O(n) Visited array
subsets_ii O(nยท2โฟ) O(n) Sort + skip dups
permutations_ii O(nยทn!) O(n) Sort + skip dups
generate_parentheses O(4โฟ/โˆšn) O(n) Open/close count
word_search O(mยทnยท4^L) O(L) Grid DFS + backtrack
palindrome_partitioning O(nยท2โฟ) O(nยฒ) Palindrome check + split
letter_combinations_of_a_phone_number O(4^nยทn) O(n) Digit-to-letters map
matchsticks_to_square O(kยท2โฟ) O(n) Partition into k buckets
partition_to_k_equal_sum_subsets O(kยท2โฟ) O(n) Partition into k buckets
n_queens O(n!) O(n) Constraint satisfaction
n_queens_ii O(n!) O(n) Constraint satisfaction
word_break_ii O(2โฟ) O(n) Prefix match + memo
combination_sum_iii O(kยทC(9,k)) O(k) Fixed-size pick with sum
rat_in_a_maze O(4^(mยทn)) O(mยทn) Grid DFS + backtrack
m_coloring_problem O(m^V) O(V) Constraint coloring
sudoku_solver O(9^m) O(1) Constraint satisfaction
expression_add_operators O(4^nยทn) O(n) Expression building

Study Order

flowchart LR
    subgraph W1["Week 1: Subsets + Permutations"]
        b1["sum_xor_totals(1863) โ†’ subsets(78) โ†’ subsets_ii(90)"]
        b2["permutations(46) โ†’ permutations_ii(47)"]
    end

    subgraph W2["Week 2: Combinations"]
        b3["combination_sum(39) โ†’ combination_sum_ii(40) โ†’ combination_sum_iii(216)"]
        b4["combinations(77) โ†’ letter_combinations(17)"]
    end

    subgraph W3["Week 3: String + Grid Backtracking"]
        b5["generate_parentheses(22) โ†’ word_search(79)"]
        b6["palindrome_partitioning(131) โ†’ word_break_ii(140)"]
    end

    subgraph W4["Week 4: Partitioning"]
        b7["matchsticks_to_square(473) โ†’ partition_k_subsets(698)"]
    end

    subgraph W5["Week 5: Hard Constraint Satisfaction"]
        b8["n_queens(51) โ†’ n_queens_ii(52) โ†’ rat_in_a_maze"]
        b9["m_coloring_problem โ†’ sudoku_solver(37) โ†’ expression_add_operators(282)"]
    end

    W1 --> W2 --> W3 --> W4 --> W5

Key insight: Every backtracking problem follows choose โ†’ explore โ†’ unchoose. The difference is what you choose (index vs element), when you record a result (at every node vs only at leaves), and how you prune (sum, length, constraint checks).

Backtracking

Core Intuition

Explore all possibilities via DFS, pruning branches that can't lead to a valid solution. The key is the choice โ†’ explore โ†’ unchoose loop.

  • Subsets: Every element is either included or not
  • Combinations: Order doesn't matter; use a start index to avoid duplicates
  • Permutations: Order matters; use a used array or swap-in-place
  • Constraint satisfaction: Prune early when constraints are violated (N-Queens, Sudoku)

Decision Flowchart

flowchart TD
    A[Backtracking Problem] --> B{What are we building?}
    B -->|All subsets| C["Subset pattern<br/>no start constraint"]
    B -->|k-size groups, order irrelevant| D["Combination pattern<br/>start index, no reuse"]
    B -->|k-size groups, reuse allowed| E["Combination with reuse<br/>Combination Sum I"]
    B -->|All orderings| F["Permutation pattern<br/>used array or swap"]
    B -->|Valid string construction| G["Construction<br/>Generate Parentheses"]
    B -->|2D grid search| H["Grid DFS<br/>Word Search"]
    B -->|Partition into groups| I{Groups equal size?}
    I -->|Yes| J["Partition DP/BT<br/>Matchsticks, K Subsets"]
    I -->|No| K["Partition by rule<br/>Palindrome Partition"]
    F --> L{Duplicates in input?}
    L -->|Yes| M["Sort + skip duplicates<br/>Subsets II, Perms II"]
    L -->|No| N[Standard permutation]

Problems

Subset XOR Trick โ€” LC 1863 | Easy

  • Learn: XOR of all subsets = XOR of each element ร— 2^(n-1); no backtracking needed
  • Mistake: Actually enumerating all 2^n subsets
  • Complexity: O(n) time, O(1) space
  • Edge: n=1 โ†’ nums[0]; empty array โ†’ 0

Subsets โ€” LC 78 | Medium

  • Learn: At each index, branch: include or skip; or iteratively add each element to existing subsets
  • Mistake: Forgetting to add the empty subset
  • Complexity: O(nยท2^n) time, O(n) space (recursion stack)
  • Edge: Empty array โ†’ [[]]

Combination Sum โ€” LC 39 | Medium

  • Learn: Unbounded โ€” same element can be reused; pass same start index on reuse
  • Mistake: Incrementing start (prevents reuse); not pruning when current sum > target
  • Complexity: O(2^(target/min)) time, O(target/min) space
  • Edge: No valid combination โ†’ []; target=0 โ†’ [[]]

Combination Sum II โ€” LC 40 | Medium

  • Learn: Each element used once; sort + skip duplicates at same recursion level
  • Mistake: Skipping duplicates across levels (removes valid combos); only skip at same depth
  • Complexity: O(2^n) time, O(n) space
  • Edge: Duplicate elements in input; target not achievable

Combinations โ€” LC 77 | Medium

  • Learn: Choose k numbers from [1..n]; classic combination with start index
  • Mistake: Not pruning when remaining numbers < k needed
  • Complexity: O(C(n,k)ยทk) time, O(k) space
  • Edge: k=n โ†’ single combination; k=0 โ†’ [[]]

Permutations โ€” LC 46 | Medium

  • Learn: Use used[] boolean array or swap elements in-place
  • Mistake: Using a set for visited (slower); not restoring state after recursion
  • Complexity: O(nยทn!) time, O(n) space
  • Edge: Single element โ†’ [[element]]

Subsets II โ€” LC 90 | Medium

  • Learn: Sort input; skip nums[i] == nums[i-1] when i > start (same level duplicate)
  • Mistake: Skipping when i > 0 (too aggressive โ€” removes valid subsets from deeper levels)
  • Complexity: O(nยท2^n) time, O(n) space
  • Edge: All duplicates โ†’ subsets of unique counts

Permutations II โ€” LC 47 | Medium

  • Learn: Sort + skip if nums[i]==nums[i-1] and not used[i-1] (ensures canonical order)
  • Mistake: Using used[i-1] condition backwards; both used[i-1] and not used[i-1] are valid but have different semantics
  • Complexity: O(nยทn!) time, O(n) space
  • Edge: All same elements โ†’ single permutation

Generate Parentheses โ€” LC 22 | Medium

  • Learn: Track open and close counts; add '(' if open < n, add ')' if close < open
  • Mistake: Not pruning invalid states early
  • Complexity: O(4^n / โˆšn) time (Catalan number), O(n) space
  • Edge: n=0 โ†’ [""] or []; n=1 โ†’ ["()"]

Word Search โ€” LC 79 | Medium

  • Learn: DFS from each cell; mark visited in-place (modify grid, restore after)
  • Mistake: Not restoring the cell after backtracking; revisiting same cell in path
  • Complexity: O(mยทnยท4^L) time where L=word length, O(L) space
  • Edge: Word longer than grid cells; single cell grid

Palindrome Partitioning โ€” LC 131 | Medium

  • Learn: At each index, try all substrings starting here; add to path if palindrome
  • Mistake: Recomputing palindrome check โ€” precompute with DP or expand-around-center
  • Complexity: O(nยท2^n) time, O(n) space
  • Edge: Single character โ†’ always palindrome; all same chars

Letter Combinations of Phone Number โ€” LC 17 | Medium

  • Learn: Map digits to letters; standard combination building
  • Mistake: Not handling empty input (return [])
  • Complexity: O(4^nยทn) time, O(n) space
  • Edge: Empty string โ†’ []; digit '1' has no letters

Matchsticks to Square โ€” LC 473 | Medium

  • Learn: Partition into 4 equal subsets; sort descending for pruning; skip duplicate bucket sums
  • Mistake: Not sorting (poor pruning); not skipping when two buckets have same current sum
  • Complexity: O(4^n) time, O(n) space
  • Edge: Total not divisible by 4 โ†’ false; single stick = perimeter/4

Partition to K Equal Sum Subsets โ€” LC 698 | Medium

  • Learn: Generalization of matchsticks; sort descending; skip duplicate bucket states
  • Mistake: Not pruning when a bucket sum exceeds target; not skipping equal-sum buckets
  • Complexity: O(k^n) time, O(n) space
  • Edge: Total not divisible by k โ†’ false; k=1 โ†’ always true

N-Queens โ€” LC 51 | Hard

  • Learn: Track columns, diagonals (r-c), anti-diagonals (r+c) in sets; place queen row by row
  • Mistake: Using O(nยฒ) board scan to check validity instead of O(1) sets
  • Complexity: O(n!) time, O(n) space
  • Edge: n=1 โ†’ [["Q"]]; n=2,3 โ†’ no solution

N-Queens II โ€” LC 52 | Hard

  • Learn: Same as N-Queens but only count solutions
  • Mistake: Building full board strings when only count needed
  • Complexity: O(n!) time, O(n) space
  • Edge: Same as N-Queens

Word Break II โ€” LC 140 | Hard

  • Learn: Backtracking + memoization; cache which start indices lead to valid completions
  • Mistake: Pure backtracking without memo โ†’ TLE on adversarial inputs
  • Complexity: O(nยท2^n) worst case, much better with memo; O(nยฒ) space
  • Edge: No valid segmentation โ†’ []; single word equals s

General Edge Cases

  • Empty input โ†’ return [[]] for subsets, [] for combinations/permutations
  • Duplicate elements โ†’ always sort first
  • Large n โ†’ check if pruning is sufficient or memoization needed
  • Restoring state: always undo mutations (grid cells, used[], path.pop())
  • Avoid modifying the input array permanently
# Problem LC Difficulty
1 Sum of All Subsets XOR Total 1863 ๐ŸŸข Easy
2 Subsets 78 ๐ŸŸก Medium
3 Combination Sum 39 ๐ŸŸก Medium
4 Combination Sum II 40 ๐ŸŸก Medium
5 Combinations 77 ๐ŸŸก Medium
6 Permutations 46 ๐ŸŸก Medium
7 Subsets II 90 ๐ŸŸก Medium
8 Permutations II 47 ๐ŸŸก Medium
9 Generate Parentheses 22 ๐ŸŸก Medium
10 Word Search 79 ๐ŸŸก Medium
11 Palindrome Partitioning 131 ๐ŸŸก Medium
12 Letter Combinations of Phone Number 17 ๐ŸŸก Medium
13 Matchsticks to Square 473 ๐ŸŸก Medium
14 Partition to K Equal Sum Subsets 698 ๐ŸŸก Medium
15 N Queens 51 ๐Ÿ”ด Hard
16 N Queens II 52 ๐Ÿ”ด Hard
17 Word Break II 140 ๐Ÿ”ด Hard
18 Combination Sum III 216 ๐ŸŸก Medium
19 Rat in a Maze โ€” ๐ŸŸก Medium
20 M Coloring Problem โ€” ๐ŸŸก Medium
21 Sudoku Solver 37 ๐Ÿ”ด Hard
22 Expression Add Operators 282 ๐Ÿ”ด Hard
Combination Sum โ–ผ expand
"""
# LC 39: Combination Sum

Given an array of distinct integers `candidates` and a target integer `target`, return
a list of all unique combinations of candidates where the chosen numbers sum to target.
The same number may be chosen an unlimited number of times. Two combinations are unique
if the frequency of at least one chosen number differs.

## Examples
```text
Input:  candidates = [2, 3, 6, 7], target = 7
Output: [[2, 2, 3], [7]]

Input:  candidates = [2, 3, 5], target = 8
Output: [[2, 2, 2, 2], [2, 3, 3], [3, 5]]

Input:  candidates = [2], target = 1
Output: []
```

## Constraints
- 1 <= candidates.length <= 30
- 2 <= candidates[i] <= 40
- All elements of candidates are distinct.
- 1 <= target <= 40
"""
from typing import List


class Solution:
    def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
        """
        ## 39. Combination Sum

        Given an array of distinct integers candidates and a target integer target,
        return a list of all unique combinations of candidates where the chosen numbers
        sum to target. You may return the combinations in any order.

        The same number may be chosen from candidates an unlimited number of times.
        Two combinations are unique if the frequency of at least one of the chosen
        numbers is different.

        ### Examples

        ```text
        Input:  candidates = [2, 3, 6, 7], target = 7
        Output: [[2, 2, 3], [7]]

        Input:  candidates = [2, 3, 5], target = 8
        Output: [[2, 2, 2, 2], [2, 3, 3], [3, 5]]

        Input:  candidates = [2], target = 1
        Output: []
        ```

        ### Constraints
        - `1 <= candidates.length <= 30`
        - `2 <= candidates[i] <= 40`
        - All elements of candidates are distinct.
        - `1 <= target <= 40`
        """
        # Candidates can be reused: recurse with same index i
        # Prune when remaining < 0; record when remaining == 0
        # O(n^(t/m)) time where t=target, m=min candidate; O(t/m) stack space
        candidates.sort()
        result = []

        def bt(start, remaining, current):
            if remaining == 0:
                result.append(current[:])
                return
            for i in range(start, len(candidates)):
                if candidates[i] > remaining:
                    break
                current.append(candidates[i])
                bt(i, remaining - candidates[i], current)
                current.pop()

        bt(0, target, [])
        return result


if __name__ == '__main__':
    s = Solution()
    assert sorted(s.combinationSum([2, 3, 6, 7], 7)) == sorted([[2, 2, 3], [7]])
    assert sorted(s.combinationSum([2, 3, 5], 8)) == sorted([[2, 2, 2, 2], [2, 3, 3], [3, 5]])
    assert s.combinationSum([2], 1) == []
    print("All tests passed.")
Combination Sum Ii โ–ผ expand
"""
# LC 40: Combination Sum II

Given a collection of candidate numbers (which may contain duplicates) and a target
number, find all unique combinations where the candidates sum to target. Each number
may be used at most once, and the solution set must not contain duplicate combinations.

## Examples
```text
Input:  candidates = [10, 1, 2, 7, 6, 1, 5], target = 8
Output: [[1, 1, 6], [1, 2, 5], [1, 7], [2, 6]]

Input:  candidates = [2, 5, 2, 1, 2], target = 5
Output: [[1, 2, 2], [5]]
```

## Constraints
- 1 <= candidates.length <= 100
- 1 <= candidates[i] <= 50
- 1 <= target <= 30
"""
from typing import List


class Solution:
    def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
        """
        ## 40. Combination Sum II

        Given a collection of candidate numbers (candidates) and a target number (target),
        find all unique combinations in candidates where the candidate numbers sum to target.

        Each number in candidates may only be used once in the combination.
        Note: The solution set must not contain duplicate combinations.

        ### Examples

        ```text
        Input:  candidates = [10, 1, 2, 7, 6, 1, 5], target = 8
        Output: [[1, 1, 6], [1, 2, 5], [1, 7], [2, 6]]

        Input:  candidates = [2, 5, 2, 1, 2], target = 5
        Output: [[1, 2, 2], [5]]
        ```

        ### Constraints
        - `1 <= candidates.length <= 100`
        - `1 <= candidates[i] <= 50`
        - `1 <= target <= 30`
        """
        # Sort so duplicates are adjacent; skip duplicate values at same level
        # Each candidate used at most once: recurse with i+1
        # O(2^n) time, O(n) stack space
        candidates.sort()
        result = []

        def bt(start, remaining, current):
            if remaining == 0:
                result.append(current[:])
                return
            for i in range(start, len(candidates)):
                if candidates[i] > remaining:
                    break
                if i > start and candidates[i] == candidates[i - 1]:
                    continue
                current.append(candidates[i])
                bt(i + 1, remaining - candidates[i], current)
                current.pop()

        bt(0, target, [])
        return result


if __name__ == '__main__':
    s = Solution()
    assert sorted(s.combinationSum2([10, 1, 2, 7, 6, 1, 5], 8)) == sorted([[1, 1, 6], [1, 2, 5], [1, 7], [2, 6]])
    assert sorted(s.combinationSum2([2, 5, 2, 1, 2], 5)) == sorted([[1, 2, 2], [5]])
    print("All tests passed.")
Combination Sum Iii โ–ผ expand
"""
LC 216 - Combination Sum III

Problem: Find all combinations of exactly k numbers from 1-9 that sum to n.
Each number may be used at most once.

Examples:
    k=3, n=7  -> [[1,2,4]]
    k=3, n=9  -> [[1,2,6],[1,3,5],[2,3,4]]

Constraints:
    2 <= k <= 9
    1 <= n <= 60

Approach: Backtracking O(C(9,k)) โ€” pick from 1-9 in increasing order,
    prune branch when current sum exceeds n or remaining slots can't be filled.
"""

from typing import List


class Solution:
    def combinationSum3(self, k: int, n: int) -> List[List[int]]:
        result = []

        def backtrack(start: int, combo: List[int], remaining: int) -> None:
            # Found valid combination of exactly k numbers summing to n
            if len(combo) == k and remaining == 0:
                result.append(combo[:])
                return
            # Prune: too many numbers already, or impossible to reach sum
            if len(combo) == k or remaining < 0:
                return
            for num in range(start, 10):
                combo.append(num)
                backtrack(num + 1, combo, remaining - num)  # no reuse: start = num+1
                combo.pop()

        backtrack(1, [], n)
        return result


if __name__ == '__main__':
    sol = Solution()
    assert sol.combinationSum3(3, 7) == [[1, 2, 4]]
    assert sol.combinationSum3(3, 9) == [[1, 2, 6], [1, 3, 5], [2, 3, 4]]
    assert sol.combinationSum3(4, 1) == []
    print("All tests passed.")
Combinations โ–ผ expand
"""
# LC 77: Combinations

Given two integers `n` and `k`, return all possible combinations of `k` numbers chosen
from the range `[1, n]`. The answer may be returned in any order.

## Examples
```text
Input:  n = 4, k = 2
Output: [[1,2],[1,3],[1,4],[2,3],[2,4],[3,4]]

Input:  n = 1, k = 1
Output: [[1]]
```

## Constraints
- 1 <= n <= 20
- 1 <= k <= n
"""
from typing import List


class Solution:
    def combine(self, n: int, k: int) -> List[List[int]]:
        """
        ## 77. Combinations

        Given two integers n and k, return all possible combinations of k numbers
        chosen from the range [1, n]. You may return the answer in any order.

        ### Examples

        ```text
        Input:  n = 4, k = 2
        Output: [[1,2],[1,3],[1,4],[2,3],[2,4],[3,4]]

        Input:  n = 1, k = 1
        Output: [[1]]
        ```

        ### Constraints
        - `1 <= n <= 20`
        - `1 <= k <= n`
        """
        # Choose k numbers from [1..n]; start from 'start' to avoid duplicates
        # Prune: if remaining numbers < k - len(path), can't complete โ€” stop early
        # O(C(n,k) * k) time, O(k) stack space
        result = []

        def bt(start, current):
            if len(current) == k:
                result.append(current[:])
                return
            # prune: need at least (k - len(current)) more numbers
            for i in range(start, n - (k - len(current)) + 2):
                current.append(i)
                bt(i + 1, current)
                current.pop()

        bt(1, [])
        return result


if __name__ == '__main__':
    s = Solution()
    assert sorted(s.combine(4, 2)) == sorted([[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]])
    assert s.combine(1, 1) == [[1]]
    print("All tests passed.")
Expression Add Operators โ–ผ expand
"""
# LC 282: Expression Add Operators

Given a string of digits and a target value, add binary operators (+, -, *)
between the digits to make the expression evaluate to the target.
Return all valid expressions.

## Examples

```text
Input:  num = "123", target = 6
Output: ["1+2+3", "1*2*3"]

Input:  num = "232", target = 8
Output: ["2*3+2", "2+3*2"]

Input:  num = "3456237490", target = 9191
Output: []
```

## Constraints
- 1 <= num.length <= 10
- num consists of only digits
- -2^31 <= target <= 2^31 - 1
"""
from typing import List


class Solution:
    def addOperators(self, num: str, target: int) -> List[str]:
        # Backtracking: at each position, choose how many digits to take as operand
        # Track: current expression string, running total, previous operand (for * precedence)
        # Key insight for multiplication: undo previous addition, apply multiply
        #   e.g., "2+3*4" = (2+3) then *4 โ†’ need to undo +3: (5-3) + (3*4) = 14
        result = []

        def backtrack(idx, expr, total, prev):
            # Base case: used all digits
            if idx == len(num):
                if total == target:
                    result.append(expr)
                return

            # Try all possible operands starting at idx
            for i in range(idx, len(num)):
                # No leading zeros (except "0" itself)
                if i > idx and num[idx] == '0':
                    break

                curr_str = num[idx:i + 1]
                curr_val = int(curr_str)

                if idx == 0:
                    # First number: no operator before it
                    backtrack(i + 1, curr_str, curr_val, curr_val)
                else:
                    # Try +
                    backtrack(i + 1, expr + '+' + curr_str,
                              total + curr_val, curr_val)
                    # Try -
                    backtrack(i + 1, expr + '-' + curr_str,
                              total - curr_val, -curr_val)
                    # Try * (undo prev, apply prev*curr)
                    backtrack(i + 1, expr + '*' + curr_str,
                              total - prev + prev * curr_val, prev * curr_val)

        backtrack(0, "", 0, 0)
        return result


if __name__ == "__main__":
    sol = Solution()
    print(sol.addOperators("123", 6))    # ["1+2+3", "1*2*3"]
    print(sol.addOperators("232", 8))    # ["2*3+2", "2+3*2"]
    print(sol.addOperators("105", 5))    # ["1*0+5", "10-5"]
Generate Parentheses โ–ผ expand
"""
# LC 22: Generate Parentheses

Given `n` pairs of parentheses, generate all combinations of well-formed parentheses.

## Examples
```text
Input:  n = 3
Output: ["((()))","(()())","(())()","()(())","()()()"]

Input:  n = 1
Output: ["()"]
```

## Constraints
- 1 <= n <= 8
"""
from typing import List


class Solution:
    def generateParenthesis(self, n: int) -> List[str]:
        """
        ## 22. Generate Parentheses

        Given n pairs of parentheses, write a function to generate all combinations
        of well-formed parentheses.

        ### Examples

        ```text
        Input:  n = 3
        Output: ["((()))","(()())","(())()","()(())","()()()"]

        Input:  n = 1
        Output: ["()"]
        ```

        ### Constraints
        - `1 <= n <= 8`
        """
        # Add '(' if open count < n; add ')' if close count < open count
        # Valid string when both counts reach n
        # O(4^n / sqrt(n)) time (Catalan number), O(n) stack space
        result = []

        def bt(current, open_count, close_count):
            if len(current) == 2 * n:
                result.append(current)
                return
            if open_count < n:
                bt(current + '(', open_count + 1, close_count)
            if close_count < open_count:
                bt(current + ')', open_count, close_count + 1)

        bt('', 0, 0)
        return result


if __name__ == '__main__':
    s = Solution()
    assert sorted(s.generateParenthesis(3)) == sorted(["((()))", "(()())", "(())()", "()(())", "()()()"])
    assert s.generateParenthesis(1) == ["()"]
    print("All tests passed.")
Letter Combinations Of A Phone Number โ–ผ expand
"""
# LC 17: Letter Combinations of a Phone Number

Given a string containing digits from 2-9, return all possible letter combinations that
the number could represent, using the standard telephone keypad mapping. Answer may be
returned in any order.

## Examples
```text
Input:  digits = "23"
Output: ["ad","ae","af","bd","be","bf","cd","ce","cf"]

Input:  digits = ""
Output: []

Input:  digits = "2"
Output: ["a","b","c"]
```

## Constraints
- 0 <= digits.length <= 4
- digits[i] is a digit in the range ['2', '9'].
"""
from typing import List
from collections import deque


class Solution:
    PHONE = {
        '2': 'abc', '3': 'def', '4': 'ghi', '5': 'jkl',
        '6': 'mno', '7': 'pqrs', '8': 'tuv', '9': 'wxyz'
    }

    def letterCombinations_bt(self, digits: str) -> List[str]:
        """
        ## 17. Letter Combinations of a Phone Number

        Given a string containing digits from 2-9 inclusive, return all possible letter
        combinations that the number could represent. Return the answer in any order.

        A mapping of digits to letters (just like on the telephone buttons) is given below.
        Note that 1 does not map to any letters.

        ### Examples

        ```text
        Input:  digits = "23"
        Output: ["ad","ae","af","bd","be","bf","cd","ce","cf"]

        Input:  digits = ""
        Output: []

        Input:  digits = "2"
        Output: ["a","b","c"]
        ```

        ### Constraints
        - `0 <= digits.length <= 4`
        - `digits[i]` is a digit in the range `['2', '9']`.
        """
        # O(4^n * n) time, O(n) stack space
        if not digits:
            return []
        result = []

        def bt(idx, current):
            if idx == len(digits):
                result.append(current)
                return
            for ch in self.PHONE[digits[idx]]:
                bt(idx + 1, current + ch)

        bt(0, '')
        return result

    def letterCombinations(self, digits: str) -> List[str]:
        """Iterative BFS: expand combinations level by level."""
        # Map each digit to its letters; DFS appending one letter per digit
        # Add to result when path length equals number of digits
        # O(4^n * n) time, O(4^n) space
        if not digits:
            return []
        queue = deque([''])
        for digit in digits:
            for _ in range(len(queue)):
                prefix = queue.popleft()
                for ch in self.PHONE[digit]:
                    queue.append(prefix + ch)
        return list(queue)


if __name__ == '__main__':
    s = Solution()
    expected = sorted(["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"])
    for fn in (s.letterCombinations_bt, s.letterCombinations):
        assert sorted(fn("23")) == expected
        assert fn("") == []
        assert sorted(fn("2")) == ["a", "b", "c"]
    print("All tests passed.")
M Coloring Problem โ–ผ expand
"""
# M Coloring Problem

Given an undirected graph and m colors, determine if the graph can be colored
with at most m colors such that no two adjacent vertices share the same color.

## Examples

```text
Input:  n=4, edges=[[0,1],[1,2],[2,3],[3,0],[0,2]], m=3
Output: True

Input:  n=3, edges=[[0,1],[1,2],[0,2]], m=2
Output: False (triangle needs 3 colors)
```

## Constraints
- 1 <= n <= 20
- 1 <= m <= n
"""
from typing import List


class Solution:
    def graphColoring(self, n: int, edges: List[List[int]], m: int) -> bool:
        # Build adjacency list
        graph = [[] for _ in range(n)]
        for u, v in edges:
            graph[u].append(v)
            graph[v].append(u)

        # color[i] = color assigned to node i (0 = uncolored)
        color = [0] * n

        def is_safe(node, c):
            # Check if assigning color c to node is valid
            # No adjacent node should have the same color
            for neighbor in graph[node]:
                if color[neighbor] == c:
                    return False
            return True

        def backtrack(node):
            # All nodes colored successfully
            if node == n:
                return True

            # Try each color 1..m for current node
            for c in range(1, m + 1):
                if is_safe(node, c):
                    color[node] = c
                    if backtrack(node + 1):
                        return True
                    color[node] = 0  # Backtrack
            return False

        return backtrack(0)


if __name__ == "__main__":
    sol = Solution()
    print(sol.graphColoring(4, [[0,1],[1,2],[2,3],[3,0],[0,2]], 3))  # True
    print(sol.graphColoring(3, [[0,1],[1,2],[0,2]], 2))              # False
Matchsticks To Square โ–ผ expand
"""
# LC 473: Matchsticks to Square

Given an array `matchsticks` of stick lengths, determine whether you can use every
matchstick exactly once to form a square (each of the four sides equal in length).
You may not break any stick. Return true if a square can be formed, otherwise false.

## Examples
```text
Input:  matchsticks = [1, 1, 2, 2, 2]
Output: true

Input:  matchsticks = [3, 3, 3, 3, 4]
Output: false
```

## Constraints
- 1 <= matchsticks.length <= 15
- 1 <= matchsticks[i] <= 10^8
"""
from typing import List


class Solution:
    def makesquare(self, matchsticks: List[int]) -> bool:
        """
        ## 473. Matchsticks to Square

        You are given an integer array matchsticks where matchsticks[i] is the length of
        the ith matchstick. You want to use all the matchsticks to make one square. You
        should not break any stick, but you can link them up, and each matchstick must be
        used exactly one time.

        Return true if you can make this square and false otherwise.

        ### Examples

        ```text
        Input:  matchsticks = [1, 1, 2, 2, 2]
        Output: true
        Explanation: 1+2=3, 1+2=3, 2=2... wait, side = 8/4 = 2. [2],[2],[2],[1,1] -> true

        Input:  matchsticks = [3, 3, 3, 3, 4]
        Output: false
        ```

        ### Constraints
        - `1 <= matchsticks.length <= 15`
        - `1 <= matchsticks[i] <= 10^8`
        """
        # Special case of partition into 4 equal subsets (k=4)
        # Sort descending for better pruning; skip duplicate bucket sums
        # O(4^n) time, O(n) stack space
        total = sum(matchsticks)
        if total % 4 != 0:
            return False
        side = total // 4
        matchsticks.sort(reverse=True)
        if matchsticks[0] > side:
            return False
        sides = [0] * 4

        def bt(idx):
            if idx == len(matchsticks):
                return sides[0] == sides[1] == sides[2] == side
            for i in range(4):
                if sides[i] + matchsticks[idx] <= side:
                    sides[i] += matchsticks[idx]
                    if bt(idx + 1):
                        return True
                    sides[i] -= matchsticks[idx]
                    if sides[i] == 0:
                        break  # prune symmetric branches
            return False

        return bt(0)


if __name__ == '__main__':
    s = Solution()
    assert s.makesquare([1, 1, 2, 2, 2]) is True
    assert s.makesquare([3, 3, 3, 3, 4]) is False
    assert s.makesquare([1, 1, 1, 1]) is True
    print("All tests passed.")
N Queens โ–ผ expand
"""
# LC 51: N-Queens

The n-queens puzzle is the problem of placing `n` queens on an `n x n` chessboard so
that no two queens attack each other. Given an integer `n`, return all distinct
solutions. Each solution is a board configuration where 'Q' marks a queen and '.'
marks an empty cell.

## Examples
```text
Input:  n = 4
Output: [[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]

Input:  n = 1
Output: [["Q"]]
```

## Constraints
- 1 <= n <= 9
"""
from typing import List


class Solution:
    def solveNQueens(self, n: int) -> List[List[str]]:
        """
        ## 51. N-Queens

        The n-queens puzzle is the problem of placing n queens on an n x n chessboard
        such that no two queens attack each other.

        Given an integer n, return all distinct solutions to the n-queens puzzle.
        You may return the answer in any order.

        Each solution contains a distinct board configuration of the n-queens' placement,
        where 'Q' and '.' both indicate a queen and an empty space, respectively.

        ### Examples

        ```text
        Input:  n = 4
        Output: [[".Q..","...Q","Q...","..Q."],
                 ["..Q.","Q...","...Q",".Q.."]]

        Input:  n = 1
        Output: [["Q"]]
        ```

        ### Constraints
        - `1 <= n <= 9`
        """
        # Place one queen per row; track occupied columns, diagonals, anti-diagonals
        # Backtrack by removing queen and clearing its column/diagonal markers
        # O(n!) time, O(n) space for tracking sets
        result = []
        cols, diag1, diag2 = set(), set(), set()
        board = [['.' ] * n for _ in range(n)]

        def bt(row):
            if row == n:
                result.append([''.join(r) for r in board])
                return
            for col in range(n):
                if col in cols or (row - col) in diag1 or (row + col) in diag2:
                    continue
                cols.add(col); diag1.add(row - col); diag2.add(row + col)
                board[row][col] = 'Q'
                bt(row + 1)
                board[row][col] = '.'
                cols.remove(col); diag1.remove(row - col); diag2.remove(row + col)

        bt(0)
        return result


if __name__ == '__main__':
    s = Solution()
    res4 = s.solveNQueens(4)
    assert len(res4) == 2
    assert sorted(res4) == sorted([[".Q..", "...Q", "Q...", "..Q."], ["..Q.", "Q...", "...Q", ".Q.."]])
    assert s.solveNQueens(1) == [["Q"]]
    print("All tests passed.")
N Queens Ii โ–ผ expand
"""
# LC 52: N-Queens II

The n-queens puzzle is the problem of placing `n` queens on an `n x n` chessboard so
that no two queens attack each other. Given an integer `n`, return the number of
distinct solutions to the n-queens puzzle.

## Examples
```text
Input:  n = 4
Output: 2

Input:  n = 1
Output: 1
```

## Constraints
- 1 <= n <= 9
"""
class Solution:
    def totalNQueens(self, n: int) -> int:
        """
        ## 52. N-Queens II

        The n-queens puzzle is the problem of placing n queens on an n x n chessboard
        such that no two queens attack each other.

        Given an integer n, return the number of distinct solutions to the n-queens puzzle.

        ### Examples

        ```text
        Input:  n = 4
        Output: 2

        Input:  n = 1
        Output: 1
        ```

        ### Constraints
        - `1 <= n <= 9`
        """
        # Same as N-Queens but only count solutions, don't store board state
        # O(n!) time, O(n) space
        cols, diag1, diag2 = set(), set(), set()
        count = 0

        def bt(row):
            nonlocal count
            if row == n:
                count += 1
                return
            for col in range(n):
                if col in cols or (row - col) in diag1 or (row + col) in diag2:
                    continue
                cols.add(col); diag1.add(row - col); diag2.add(row + col)
                bt(row + 1)
                cols.remove(col); diag1.remove(row - col); diag2.remove(row + col)

        bt(0)
        return count


if __name__ == '__main__':
    s = Solution()
    assert s.totalNQueens(4) == 2
    assert s.totalNQueens(1) == 1
    assert s.totalNQueens(8) == 92
    print("All tests passed.")
Palindrome Partitioning โ–ผ expand
"""
# LC 131: Palindrome Partitioning

Given a string `s`, partition it such that every substring of the partition is a
palindrome. Return all possible palindrome partitionings of `s`.

## Examples
```text
Input:  s = "aab"
Output: [["a","a","b"],["aa","b"]]

Input:  s = "a"
Output: [["a"]]
```

## Constraints
- 1 <= s.length <= 16
- s contains only lowercase English letters.
"""
from typing import List


class Solution:
    def partition(self, s: str) -> List[List[str]]:
        """
        ## 131. Palindrome Partitioning

        Given a string s, partition s such that every substring of the partition is a
        palindrome. Return all possible palindrome partitioning of s.

        ### Examples

        ```text
        Input:  s = "aab"
        Output: [["a","a","b"],["aa","b"]]

        Input:  s = "a"
        Output: [["a"]]
        ```

        ### Constraints
        - `1 <= s.length <= 16`
        - s consists only of lowercase English letters.
        """
        # Try every prefix of remaining string; if it's a palindrome, recurse on suffix
        # Add complete partition to result when entire string is consumed
        # O(n * 2^n) time, O(n) stack space
        result = []

        def is_palindrome(sub):
            return sub == sub[::-1]

        def bt(start, current):
            if start == len(s):
                result.append(current[:])
                return
            for end in range(start + 1, len(s) + 1):
                substr = s[start:end]
                if is_palindrome(substr):
                    current.append(substr)
                    bt(end, current)
                    current.pop()

        bt(0, [])
        return result


if __name__ == '__main__':
    s = Solution()
    assert sorted(s.partition("aab")) == sorted([["a", "a", "b"], ["aa", "b"]])
    assert s.partition("a") == [["a"]]
    print("All tests passed.")
Partition To K Equal Sum Subsets โ–ผ expand
"""
# LC 698: Partition to K Equal Sum Subsets

Given an integer array `nums` and an integer `k`, return true if it is possible to
divide the array into `k` non-empty subsets whose sums are all equal.

## Examples
```text
Input:  nums = [4, 3, 2, 3, 5, 2, 1], k = 4
Output: true

Input:  nums = [1, 2, 3, 4], k = 3
Output: false
```

## Constraints
- 1 <= k <= nums.length <= 16
- 1 <= nums[i] <= 10^4
- The frequency of each element is in the range [1, 4].
"""
from typing import List


class Solution:
    def canPartitionKSubsets(self, nums: List[int], k: int) -> bool:
        """
        ## 698. Partition to K Equal Sum Subsets

        Given an integer array nums and an integer k, return true if it is possible to
        divide this array into k non-empty subsets whose sums are all equal.

        ### Examples

        ```text
        Input:  nums = [4, 3, 2, 3, 5, 2, 1], k = 4
        Output: true
        Explanation: (5), (1,4), (2,3), (2,3)

        Input:  nums = [1, 2, 3, 4], k = 3
        Output: false
        ```

        ### Constraints
        - `1 <= k <= nums.length <= 16`
        - `1 <= nums[i] <= 10^4`
        - The frequency of each element is in the range [1, 4].
        """
        # Try placing each unused number into one of k buckets
        # Skip bucket if it already has the same sum as a previous bucket (pruning)
        # Return True when all k buckets reach target sum
        # O(k * 2^n) time with bitmask memoization, O(2^n) space
        total = sum(nums)
        if total % k != 0:
            return False
        target = total // k
        nums.sort(reverse=True)
        if nums[0] > target:
            return False

        n = len(nums)
        memo = {}

        def bt(mask, current_sum):
            if mask == (1 << n) - 1:
                return True
            if mask in memo:
                return memo[mask]
            for i in range(n):
                if mask & (1 << i):
                    continue
                next_sum = current_sum + nums[i]
                if next_sum > target:
                    continue
                new_mask = mask | (1 << i)
                if bt(new_mask, next_sum % target):
                    memo[mask] = True
                    return True
            memo[mask] = False
            return False

        return bt(0, 0)


if __name__ == '__main__':
    s = Solution()
    assert s.canPartitionKSubsets([4, 3, 2, 3, 5, 2, 1], 4) is True
    assert s.canPartitionKSubsets([1, 2, 3, 4], 3) is False
    assert s.canPartitionKSubsets([2, 2, 2, 2], 2) is True
    print("All tests passed.")
Permutations โ–ผ expand
"""
# LC 46: Permutations

Given an array `nums` of distinct integers, return all the possible permutations. The
answer may be returned in any order.

## Examples
```text
Input:  nums = [1, 2, 3]
Output: [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

Input:  nums = [1]
Output: [[1]]
```

## Constraints
- 1 <= nums.length <= 6
- -10 <= nums[i] <= 10
- All the integers of nums are unique.
"""
from typing import List


class Solution:
    def permute_used(self, nums: List[int]) -> List[List[int]]:
        """
        ## 46. Permutations

        Given an array nums of distinct integers, return all the possible permutations.
        You can return the answer in any order.

        ### Examples

        ```text
        Input:  nums = [1, 2, 3]
        Output: [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

        Input:  nums = [0, 1]
        Output: [[0,1],[1,0]]

        Input:  nums = [1]
        Output: [[1]]
        ```

        ### Constraints
        - `1 <= nums.length <= 6`
        - `-10 <= nums[i] <= 10`
        - All integers of nums are unique.
        """
        # O(n!) time, O(n) extra space for used set
        result = []
        used = [False] * len(nums)

        def bt(current):
            if len(current) == len(nums):
                result.append(current[:])
                return
            for i in range(len(nums)):
                if not used[i]:
                    used[i] = True
                    current.append(nums[i])
                    bt(current)
                    current.pop()
                    used[i] = False

        bt([])
        return result

    def permute(self, nums: List[int]) -> List[List[int]]:
        """Swap-based backtracking: swap element into current position."""
        # Swap current index with each subsequent index to generate permutations
        # Backtrack by swapping back after recursion
        # O(n!) time, O(1) extra space (in-place swaps)
        result = []

        def bt(start):
            if start == len(nums):
                result.append(nums[:])
                return
            for i in range(start, len(nums)):
                nums[start], nums[i] = nums[i], nums[start]
                bt(start + 1)
                nums[start], nums[i] = nums[i], nums[start]

        bt(0)
        return result


if __name__ == '__main__':
    s = Solution()
    expected = [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]
    for fn in (s.permute_used, s.permute):
        assert sorted(fn([1, 2, 3])) == sorted(expected)
        assert sorted(fn([0, 1])) == sorted([[0, 1], [1, 0]])
    print("All tests passed.")
Permutations Ii โ–ผ expand
"""
# LC 47: Permutations II

Given a collection of numbers `nums` that might contain duplicates, return all possible
unique permutations in any order.

## Examples
```text
Input:  nums = [1, 1, 2]
Output: [[1,1,2],[1,2,1],[2,1,1]]

Input:  nums = [1, 2, 3]
Output: [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
```

## Constraints
- 1 <= nums.length <= 8
- -10 <= nums[i] <= 10
"""
from typing import List


class Solution:
    def permuteUnique(self, nums: List[int]) -> List[List[int]]:
        """
        ## 47. Permutations II

        Given a collection of numbers, nums, that might contain duplicates, return all
        possible unique permutations in any order.

        ### Examples

        ```text
        Input:  nums = [1, 1, 2]
        Output: [[1,1,2],[1,2,1],[2,1,1]]

        Input:  nums = [1, 2, 3]
        Output: [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
        ```

        ### Constraints
        - `1 <= nums.length <= 8`
        - `-10 <= nums[i] <= 10`
        """
        # Sort to group duplicates; use a 'used' array to track which elements are in current path
        # Skip duplicate: if nums[i]==nums[i-1] and nums[i-1] not used, skip to avoid duplicate permutations
        # O(n!) time, O(n) stack space
        nums.sort()
        result = []
        used = [False] * len(nums)

        def bt(current):
            if len(current) == len(nums):
                result.append(current[:])
                return
            for i in range(len(nums)):
                if used[i]:
                    continue
                # skip duplicate: same value as previous, and previous not used in this path
                if i > 0 and nums[i] == nums[i - 1] and not used[i - 1]:
                    continue
                used[i] = True
                current.append(nums[i])
                bt(current)
                current.pop()
                used[i] = False

        bt([])
        return result


if __name__ == '__main__':
    s = Solution()
    assert sorted(s.permuteUnique([1, 1, 2])) == sorted([[1, 1, 2], [1, 2, 1], [2, 1, 1]])
    assert len(s.permuteUnique([1, 2, 3])) == 6
    print("All tests passed.")
Rat In A Maze โ–ผ expand
"""
# Rat in a Maze

Given an nร—n maze where 1 = open cell and 0 = blocked cell, find all paths
from (0,0) to (n-1,n-1). The rat can move Down, Left, Right, Up.
Return paths as strings of directions in lexicographical order.

## Examples

```text
Input:  maze = [[1,0,0,0],[1,1,0,1],[1,1,0,0],[0,1,1,1]]
Output: ["DDRDRR","DRDDRR"]

Input:  maze = [[1,0],[1,1]]
Output: ["DR","RD"] โ€” wait, (0,1) is blocked, so only "DR" isn't valid. Actually "DR" means down then right = (0,0)->(1,0)->(1,1) = valid
```

## Constraints
- 2 <= n <= 5
- maze[0][0] == 1 and maze[n-1][n-1] == 1
"""
from typing import List


class Solution:
    def findPaths(self, maze: List[List[int]]) -> List[str]:
        # Backtracking: try all 4 directions (D,L,R,U in sorted order for lex output)
        # Mark cell visited before exploring, unmark on backtrack
        # Base case: reached (n-1, n-1) โ†’ add path to result
        n = len(maze)
        result = []
        visited = [[False] * n for _ in range(n)]

        def backtrack(r, c, path):
            # Reached destination
            if r == n - 1 and c == n - 1:
                result.append(path)
                return

            # Try D, L, R, U (lexicographic order)
            for dr, dc, d in [(1, 0, 'D'), (0, -1, 'L'), (0, 1, 'R'), (-1, 0, 'U')]:
                nr, nc = r + dr, c + dc
                if 0 <= nr < n and 0 <= nc < n and maze[nr][nc] == 1 and not visited[nr][nc]:
                    visited[nr][nc] = True
                    backtrack(nr, nc, path + d)
                    visited[nr][nc] = False  # Backtrack

        if maze[0][0] == 1:
            visited[0][0] = True
            backtrack(0, 0, "")
        return result


if __name__ == "__main__":
    sol = Solution()
    print(sol.findPaths([[1, 0, 0, 0], [1, 1, 0, 1], [1, 1, 0, 0], [0, 1, 1, 1]]))
    # Expected: ["DDRDRR", "DRDDRR"]
Subsets โ–ผ expand
"""
# LC 78: Subsets

Given an integer array `nums` of unique elements, return all possible subsets (the power
set). The solution set must not contain duplicate subsets and may be returned in any
order.

## Examples
```text
Input:  nums = [1, 2, 3]
Output: [[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]

Input:  nums = [0]
Output: [[],[0]]
```

## Constraints
- 1 <= nums.length <= 10
- -10 <= nums[i] <= 10
- All the numbers of nums are unique.
"""
from typing import List


class Solution:
    def subsets_iterative(self, nums: List[int]) -> List[List[int]]:
        """
        ## 78. Subsets

        Given an integer array nums of unique elements, return all possible subsets
        (the power set). The solution set must not contain duplicate subsets.
        Return the solution in any order.

        ### Examples

        ```text
        Input:  nums = [1, 2, 3]
        Output: [[], [1], [2], [1,2], [3], [1,3], [2,3], [1,2,3]]

        Input:  nums = [0]
        Output: [[], [0]]
        ```

        ### Constraints
        - `1 <= nums.length <= 10`
        - `-10 <= nums[i] <= 10`
        - All integers in nums are unique.
        """
        # O(2^n) time, O(2^n) space
        result = [[]]
        for num in nums:
            result += [subset + [num] for subset in result]
        return result

    def subsets(self, nums: List[int]) -> List[List[int]]:
        """Backtracking: build subsets by choosing to include or skip each element."""
        # At each index, branch: include nums[i] or skip it
        # Leaf nodes (i == len(nums)) are complete subsets โ€” add to result
        # O(2^n) time, O(n) stack space
        result = []

        def bt(start, current):
            result.append(current[:])
            for i in range(start, len(nums)):
                current.append(nums[i])
                bt(i + 1, current)
                current.pop()

        bt(0, [])
        return result


if __name__ == '__main__':
    s = Solution()
    for fn in (s.subsets_iterative, s.subsets):
        assert sorted(fn([1, 2, 3])) == sorted([[], [1], [2], [1, 2], [3], [1, 3], [2, 3], [1, 2, 3]])
        assert sorted(fn([0])) == sorted([[], [0]])
    print("All tests passed.")
Subsets Ii โ–ผ expand
"""
# LC 90: Subsets II

Given an integer array `nums` that may contain duplicates, return all possible subsets
(the power set). The solution set must not contain duplicate subsets and may be returned
in any order.

## Examples
```text
Input:  nums = [1, 2, 2]
Output: [[],[1],[1,2],[1,2,2],[2],[2,2]]

Input:  nums = [0]
Output: [[],[0]]
```

## Constraints
- 1 <= nums.length <= 10
- -10 <= nums[i] <= 10
"""
from typing import List


class Solution:
    def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:
        """
        ## 90. Subsets II

        Given an integer array nums that may contain duplicates, return all possible
        subsets (the power set). The solution set must not contain duplicate subsets.
        Return the solution in any order.

        ### Examples

        ```text
        Input:  nums = [1, 2, 2]
        Output: [[], [1], [1,2], [1,2,2], [2], [2,2]]

        Input:  nums = [0]
        Output: [[], [0]]
        ```

        ### Constraints
        - `1 <= nums.length <= 10`
        - `-10 <= nums[i] <= 10`
        """
        # Sort first so duplicates are adjacent and easy to skip
        # Skip duplicate values at the same recursion level to avoid duplicate subsets
        # O(2^n) time, O(n) stack space
        nums.sort()
        result = []

        def bt(start, current):
            result.append(current[:])
            for i in range(start, len(nums)):
                if i > start and nums[i] == nums[i - 1]:
                    continue
                current.append(nums[i])
                bt(i + 1, current)
                current.pop()

        bt(0, [])
        return result


if __name__ == '__main__':
    s = Solution()
    assert sorted(s.subsetsWithDup([1, 2, 2])) == sorted([[], [1], [1, 2], [1, 2, 2], [2], [2, 2]])
    assert sorted(s.subsetsWithDup([0])) == sorted([[], [0]])
    print("All tests passed.")
Sudoku Solver โ–ผ expand
"""
# LC 37: Sudoku Solver

Write a program to solve a Sudoku puzzle by filling the empty cells.
Each row, column, and 3ร—3 box must contain digits 1-9 without repetition.

## Examples

```text
Input:  board with '.' for empty cells
Output: board filled with valid solution
```

## Constraints
- board.length == 9
- board[i].length == 9
- board[i][j] is a digit or '.'
- Input board has exactly one solution
"""
from typing import List


class Solution:
    def solveSudoku(self, board: List[List[str]]) -> None:
        # Backtracking: find empty cell, try digits 1-9, validate, recurse
        # Optimization: use sets for rows, cols, boxes for O(1) validity check

        # Pre-populate constraint sets
        rows = [set() for _ in range(9)]
        cols = [set() for _ in range(9)]
        boxes = [set() for _ in range(9)]

        for r in range(9):
            for c in range(9):
                if board[r][c] != '.':
                    d = board[r][c]
                    rows[r].add(d)
                    cols[c].add(d)
                    boxes[(r // 3) * 3 + c // 3].add(d)

        def backtrack(pos):
            # Find next empty cell starting from pos
            while pos < 81:
                r, c = divmod(pos, 9)
                if board[r][c] == '.':
                    break
                pos += 1
            else:
                return True  # All cells filled

            r, c = divmod(pos, 9)
            box_id = (r // 3) * 3 + c // 3

            for d in '123456789':
                if d not in rows[r] and d not in cols[c] and d not in boxes[box_id]:
                    # Place digit
                    board[r][c] = d
                    rows[r].add(d)
                    cols[c].add(d)
                    boxes[box_id].add(d)

                    if backtrack(pos + 1):
                        return True

                    # Backtrack: remove digit
                    board[r][c] = '.'
                    rows[r].remove(d)
                    cols[c].remove(d)
                    boxes[box_id].remove(d)

            return False

        backtrack(0)


if __name__ == "__main__":
    sol = Solution()
    board = [
        ["5","3",".",".","7",".",".",".","."],
        ["6",".",".","1","9","5",".",".","."],
        [".","9","8",".",".",".",".","6","."],
        ["8",".",".",".","6",".",".",".","3"],
        ["4",".",".","8",".","3",".",".","1"],
        ["7",".",".",".","2",".",".",".","6"],
        [".","6",".",".",".",".","2","8","."],
        [".",".",".","4","1","9",".",".","5"],
        [".",".",".",".","8",".",".","7","9"]
    ]
    sol.solveSudoku(board)
    for row in board:
        print(row)
Sum Of All Subset Xor Totals โ–ผ expand
"""
# LC 1863: Sum of All Subset XOR Totals

The XOR total of an array is the bitwise XOR of all its elements, or 0 if the array is
empty. Given an array `nums`, return the sum of all XOR totals for every subset of
`nums`. Subsets with the same elements at different indices are counted separately.

## Examples
```text
Input:  nums = [1, 3]
Output: 6
Explanation: subsets [], [1], [3], [1,3] have XOR totals 0, 1, 3, 2 -> sum = 6

Input:  nums = [5, 1, 6]
Output: 28
```

## Constraints
- 1 <= nums.length <= 12
- 1 <= nums[i] <= 20
"""
from typing import List


class Solution:
    def subsetXORSum_brute(self, nums: List[int]) -> int:
        """
        ## 1863. Sum of All Subset XOR Totals

        The XOR total of an array is defined as the XOR of all its elements, or 0 if the
        array is empty. Given an array nums, return the sum of all XOR totals for every
        subset of nums.

        ### Examples

        ```text
        Input:  nums = [1, 3]
        Output: 6
        Explanation: subsets -> [], [1], [3], [1,3]
                     XOR    ->  0,   1,   3,    2   => sum = 6

        Input:  nums = [5, 1, 6]
        Output: 28

        Input:  nums = [3, 4, 5, 6, 7, 8]
        Output: 480
        ```

        ### Constraints
        - `1 <= nums.length <= 12`
        - `1 <= nums[i] <= 20`
        """
        # O(2^n * n) time, O(n) space
        n = len(nums)
        total = 0
        for mask in range(1 << n):
            xor = 0
            for i in range(n):
                if mask & (1 << i):
                    xor ^= nums[i]
            total += xor
        return total

    def subsetXORSum(self, nums: List[int]) -> int:
        """Bit manipulation: OR all elements, multiply by 2^(n-1)."""
        # For each element, branch: include in XOR or skip
        # Sum XOR values of all subsets at leaf nodes
        # O(n) time, O(1) space
        or_all = 0
        for x in nums:
            or_all |= x
        return or_all * (1 << (len(nums) - 1))


if __name__ == '__main__':
    s = Solution()
    assert s.subsetXORSum([1, 3]) == 6
    assert s.subsetXORSum([5, 1, 6]) == 28
    assert s.subsetXORSum([3, 4, 5, 6, 7, 8]) == 480
    assert s.subsetXORSum_brute([1, 3]) == 6
    assert s.subsetXORSum_brute([5, 1, 6]) == 28
    print("All tests passed.")
Word Break Ii โ–ผ expand
"""
# LC 140: Word Break II

Given a string `s` and a dictionary of strings `wordDict`, add spaces in `s` to
construct a sentence where each word is a valid dictionary word. Return all such
possible sentences in any order. The same dictionary word may be reused.

## Examples
```text
Input:  s = "catsanddog", wordDict = ["cat","cats","and","sand","dog"]
Output: ["cats and dog","cat sand dog"]

Input:  s = "pineapplepenapple",
        wordDict = ["apple","pen","applepen","pine","pineapple"]
Output: ["pine apple pen apple","pineapple pen apple","pine applepen apple"]
```

## Constraints
- 1 <= s.length <= 20
- 1 <= wordDict.length <= 1000
- 1 <= wordDict[i].length <= 10
"""
from typing import List


class Solution:
    def wordBreak(self, s: str, wordDict: List[str]) -> List[str]:
        """
        ## 140. Word Break II

        Given a string s and a dictionary of strings wordDict, add spaces in s to
        construct a sentence where each word is a valid dictionary word. Return all
        such possible sentences in any order.

        Note that the same word in the dictionary may be reused multiple times in
        the segmentation.

        ### Examples

        ```text
        Input:  s = "catsanddog",
                wordDict = ["cat","cats","and","sand","dog"]
        Output: ["cats and dog","cat sand dog"]

        Input:  s = "pineapplepenapple",
                wordDict = ["apple","pen","applepen","pine","pineapple"]
        Output: ["pine apple pen apple","pineapple pen apple","pine applepen apple"]

        Input:  s = "catsandog",
                wordDict = ["cats","dog","sand","and","cat"]
        Output: []
        ```

        ### Constraints
        - `1 <= s.length <= 20`
        - `1 <= wordDict.length <= 1000`
        - `1 <= wordDict[i].length <= 10`
        - s and wordDict[i] consist of only lowercase English letters.
        - All strings in wordDict are unique.
        """
        # DFS with memoization: at each position try all words that match
        # Cache results per start index to avoid recomputation
        # O(2^n) time worst case, O(n) memoization reduces repeated work; O(n) stack space
        word_set = set(wordDict)
        memo = {}

        def bt(start):
            if start in memo:
                return memo[start]
            if start == len(s):
                return ['']
            sentences = []
            for end in range(start + 1, len(s) + 1):
                word = s[start:end]
                if word in word_set:
                    for rest in bt(end):
                        sentences.append(word + (' ' + rest if rest else ''))
            memo[start] = sentences
            return sentences

        return bt(0)


if __name__ == '__main__':
    s = Solution()
    assert sorted(s.wordBreak("catsanddog", ["cat", "cats", "and", "sand", "dog"])) == sorted(["cats and dog", "cat sand dog"])
    assert sorted(s.wordBreak("pineapplepenapple", ["apple", "pen", "applepen", "pine", "pineapple"])) == sorted(["pine apple pen apple", "pineapple pen apple", "pine applepen apple"])
    assert s.wordBreak("catsandog", ["cats", "dog", "sand", "and", "cat"]) == []
    print("All tests passed.")
Word Search โ–ผ expand
"""
# LC 79: Word Search

Given an `m x n` grid of characters `board` and a string `word`, return true if `word`
exists in the grid. The word can be constructed from letters of sequentially adjacent
cells (horizontally or vertically neighboring), where the same cell may not be used
more than once.

## Examples
```text
Input:  board = [["A","B","C","E"],
                 ["S","F","C","S"],
                 ["A","D","E","E"]], word = "ABCCED"
Output: true

Input:  board = [["A","B","C","E"],
                 ["S","F","C","S"],
                 ["A","D","E","E"]], word = "ABCB"
Output: false
```

## Constraints
- m == board.length, n == board[i].length
- 1 <= m, n <= 6
- 1 <= word.length <= 15
"""
from typing import List


class Solution:
    def exist(self, board: List[List[str]], word: str) -> bool:
        """
        ## 79. Word Search

        Given an m x n grid of characters board and a string word, return true if word
        exists in the grid. The word can be constructed from letters of sequentially
        adjacent cells, where adjacent cells are horizontally or vertically neighboring.
        The same letter cell may not be used more than once.

        ### Examples

        ```text
        Input:  board = [["A","B","C","E"],
                          ["S","F","C","S"],
                          ["A","D","E","E"]], word = "ABCCED"
        Output: true

        Input:  board = [["A","B","C","E"],
                          ["S","F","C","S"],
                          ["A","D","E","E"]], word = "SEE"
        Output: true

        Input:  board = [["A","B","C","E"],
                          ["S","F","C","S"],
                          ["A","D","E","E"]], word = "ABCB"
        Output: false
        ```

        ### Constraints
        - `m == board.length`, `n = board[i].length`
        - `1 <= m, n <= 6`
        - `1 <= word.length <= 15`
        - board and word consist of only lowercase and uppercase English letters.
        """
        # DFS from each cell matching word[0]; mark cell visited by modifying grid
        # Backtrack by restoring the cell after recursion
        # O(m * n * 4^L) time where L = len(word), O(L) stack space
        m, n = len(board), len(board[0])

        def dfs(r, c, idx):
            if idx == len(word):
                return True
            if r < 0 or r >= m or c < 0 or c >= n or board[r][c] != word[idx]:
                return False
            tmp, board[r][c] = board[r][c], '#'
            found = (dfs(r + 1, c, idx + 1) or dfs(r - 1, c, idx + 1) or
                     dfs(r, c + 1, idx + 1) or dfs(r, c - 1, idx + 1))
            board[r][c] = tmp
            return found

        for i in range(m):
            for j in range(n):
                if dfs(i, j, 0):
                    return True
        return False


if __name__ == '__main__':
    s = Solution()
    board = [["A", "B", "C", "E"], ["S", "F", "C", "S"], ["A", "D", "E", "E"]]
    import copy
    assert s.exist(copy.deepcopy(board), "ABCCED") is True
    assert s.exist(copy.deepcopy(board), "SEE") is True
    assert s.exist(copy.deepcopy(board), "ABCB") is False
    print("All tests passed.")

Backtracking

Choose / Explore / Unchoose recursion tree