diff --git a/apps/docs/content/backtracking/39-combination-sum.mdx b/apps/docs/content/backtracking/39-combination-sum.mdx new file mode 100644 index 0000000..adf8d52 --- /dev/null +++ b/apps/docs/content/backtracking/39-combination-sum.mdx @@ -0,0 +1,53 @@ +--- +title: '39. Combination Sum' +description: 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 +sidebar: + label: 'Combination Sum' + badge: 'Medium' +--- + +Array + +### Example 1: +- Input: `candidates = [2,3,6,7], target = 7` +- Output: `[[2,2,3],[7]]` +- Explanation: `2` and `3` are `candidates`, and `2 + 2 + 3 = 7`. Note that `2` can be used multiple times. `7` is a candidate, and `7 = 7`. These are the only two combinations. + +### Example 2: +- Input: `candidates = [2,3,5], target = 8` +- Output: `[[2,2,2,2],[2,3,3],[3,5]]` + +### Example 3: +- Input: `candidates = [2], target = 1` +- Output: `[]` + +### Constraints: + +- `1 <= candidates.length <= 30` +- `2 <= candidates[i] <= 40` +- All elements of `candidates` are distinct. +- `1 <= target <= 40` + +## Solution + +```py +class Solution: + def combinationSum(self, candidates: list[int], target: int) -> list[list[int]]: + candidates.sort() + result, path = [], [] + + def backtrack(start, remaining): + if remaining == 0: + result.append(path[:]) + return + for i in range(start, len(candidates)): + if candidates[i] > remaining: + break + + path.append(candidates[i]) + backtrack(i, remaining - candidates[i]) + path.pop() + + backtrack(0, target) + return result +``` diff --git a/apps/docs/content/backtracking/46-permutations.mdx b/apps/docs/content/backtracking/46-permutations.mdx new file mode 100644 index 0000000..ad23c6e --- /dev/null +++ b/apps/docs/content/backtracking/46-permutations.mdx @@ -0,0 +1,52 @@ +--- +title: '46. Permutations' +description: Given an array nums of distinct integers, return all the possible permutations. You can return the answer in any order +sidebar: + label: 'Permutations' + badge: 'Medium' +--- + +Array + +### Example 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]]` + +### Example 2: +- Input: `nums = [0,1]` +- Output: `[[0,1],[1,0]]` + +### Example 3: +- Input: `nums = [1]` +- Output: `[[1]]` + +### Constraints: + +- `1 <= nums.length <= 6` +- `-10 <= nums[i] <= 10` +- All the integers of `nums` are unique. + +## Solution + +```py +class Solution: + def permute(self, nums: list[int]) -> list[list[int]]: + result, path = [], [] + used = [False] * len(nums) + + def backtrack(): + if len(path) == len(nums): + result.append(path[:]) + return + for i in range(len(nums)): + if used[i]: + continue + used[i] = True + path.append(nums[i]) + backtrack() + path.pop() + used[i] = False + + backtrack() + return result +``` diff --git a/apps/docs/content/backtracking/78-subsets.mdx b/apps/docs/content/backtracking/78-subsets.mdx new file mode 100644 index 0000000..606460b --- /dev/null +++ b/apps/docs/content/backtracking/78-subsets.mdx @@ -0,0 +1,41 @@ +--- +title: '78. Subsets' +description: Given an integer array nums of unique elements, return all possible subsets (the power set) +sidebar: + label: 'Subsets' + badge: 'Medium' +--- + +Array + +### Example 1: +- Input: `nums = [1,2,3]` +- Output: `[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]` + +### Example 2: +- Input: `nums = [0]` +- Output: `[[],[0]]` + +### Constraints: + +- `1 <= nums.length <= 10` +- `-10 <= nums[i] <= 10` +- All the numbers of `nums` are unique. + +## Solution + +```py +class Solution: + def subsets(self, nums: list[int]) -> list[list[int]]: + res = [] + + def backtrack(i, path): + res.append(path[:]) + for num_idx in range(i, len(nums)): + path.append(nums[num_idx]) + backtrack(num_idx + 1, path) + path.pop() + + backtrack(0, []) + return res +``` diff --git a/apps/docs/content/backtracking/index.mdx b/apps/docs/content/backtracking/index.mdx new file mode 100644 index 0000000..46de4ce --- /dev/null +++ b/apps/docs/content/backtracking/index.mdx @@ -0,0 +1,163 @@ +--- +title: Backtracking +description: 'Backtracking is a depth-first search technique that builds a solution step by step and abandons (undoes) a partial choice as soon as it can’t lead to a valid complete solution.' +hidden: true +--- + +## What it is + +Backtracking explores possible paths to a solution one step at a time. When a path reaches a dead end, you undo the last step and try a different option. Think of every problem as a decision tree. The root is an empty answer. Each level is a decision. Each branch is one option. + +## When to use it + +Use backtracking when you must generate **all** valid solutions or evaluate **all** combinations. Do not use it when you need only one solution or the optimal solution. For those, use greedy or dynamic programming. Backtracking has exponential time complexity. + +## The four components + +Every backtracking problem has the same parts: + +1. **Base case** — when to save the current path and stop. +2. **Choices** — the options at each step. +3. **Constraints** — the rules that reject an invalid path early. +4. **Backtrack step** — undo the last choice and try the next one. + +## The template + +```python +def solve(data): + result = [] + path = [] + + def backtrack(index): + # 1. Base case + if is_complete(index, path): + result.append(path[:]) # copy — lists are passed by reference + return + + # 2. Choices + for choice in get_choices(index, data): + # 3. Constraints + if not is_valid(choice, path): + continue + + path.append(choice) # make the choice + backtrack(index + 1) # explore + path.pop() # 4. Backtrack + + backtrack(0) + return result +``` + +## Steps to apply the template + +1. Draw the decision tree for a small input. Name each level and each branch. +2. Write the base case. Ask: when is the path complete? +3. Write the choices. Ask: what can I add at this level? +4. Write the constraints. Ask: what makes a path invalid? Reject it before recursion. +5. Add the backtrack step. Pop what you appended. +6. Copy the path when you save it. Never append the same list reference. + +## Four problems, one template + +### Subsets + +Choice: include or skip each number. No constraint. Base case: index equals list length. + +```python +def subsets(nums): + result, path = [], [] + + def backtrack(i): + if i == len(nums): + result.append(path[:]) + return + path.append(nums[i]) # include + backtrack(i + 1) + path.pop() + backtrack(i + 1) # skip + + backtrack(0) + return result +``` + +### Permutations + +Same choices as subsets. Constraint: do not reuse a number already in the path. Base case: path length equals list length. + +```python +def permute(nums): + result, path = [], [] + used = [False] * len(nums) + + def backtrack(): + if len(path) == len(nums): + result.append(path[:]) + return + for i in range(len(nums)): + if used[i]: + continue + used[i] = True + path.append(nums[i]) + backtrack() + path.pop() + used[i] = False + + backtrack() + return result +``` + +### Combinations + +Same as subsets. Change one line: save the path only when its length equals `k`. + +```python +def combine(n, k): + result, path = [], [] + + def backtrack(start): + if len(path) == k: + result.append(path[:]) + return + for i in range(start, n + 1): + path.append(i) + backtrack(i + 1) + path.pop() + + backtrack(1) + return result +``` + +### N-Queens + +Choice: which column to place the queen in the current row. Constraint: no shared column or diagonal. Base case: `n` queens placed. Backtrack: remove the last queen. + +```python +def solve_n_queens(n): + result, cols = [], [] + used_cols, diag1, diag2 = set(), set(), set() + + def backtrack(row): + if row == n: + result.append(cols[:]) + return + for col in range(n): + if col in used_cols or row - col in diag1 or row + col in diag2: + continue + cols.append(col) + used_cols.add(col); diag1.add(row - col); diag2.add(row + col) + backtrack(row + 1) + cols.pop() + used_cols.remove(col); diag1.remove(row - col); diag2.remove(row + col) + + backtrack(0) + return result +``` + +## Common mistakes + +- You append the `path` list itself instead of a copy. The result fills with empty lists. +- You forget to pop. The path grows and never resets. +- You check constraints after recursion instead of before. You waste time on dead paths. +- You use backtracking when you need only one answer. Use greedy or DP instead. + + diff --git a/apps/docs/content/backtracking/meta.ts b/apps/docs/content/backtracking/meta.ts new file mode 100644 index 0000000..5acd18c --- /dev/null +++ b/apps/docs/content/backtracking/meta.ts @@ -0,0 +1,3 @@ +import { defineMeta } from "blume"; + +export default defineMeta({ title: "Backtracking", order: 20 }); diff --git a/work/1/Medium/Array/39.combination-sum.py b/work/1/Medium/Array/39.combination-sum.py new file mode 100644 index 0000000..6f37cc0 --- /dev/null +++ b/work/1/Medium/Array/39.combination-sum.py @@ -0,0 +1,75 @@ +""" +39. Combination Sum +Difficulty: Medium +https://leetcode.com/problems/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. + +The test cases are generated such that the number of unique +combinations that sum up to target is less than 150 combinations for +the given input. + + + +Example 1: + +Input: candidates = [2,3,6,7], target = 7 +Output: [[2,2,3],[7]] +Explanation: +2 and 3 are candidates, and 2 + 2 + 3 = 7. Note that 2 can be used +multiple times. +7 is a candidate, and 7 = 7. +These are the only two combinations. + +Example 2: + +Input: candidates = [2,3,5], target = 8 +Output: [[2,2,2,2],[2,3,3],[3,5]] + +Example 3: + +Input: candidates = [2], target = 1 +Output: [] + + + +Constraints: + + • 1 <= candidates.length <= 30 + + • 2 <= candidates[i] <= 40 + + • All elements of candidates are distinct. + + • 1 <= target <= 40 +""" + + +class Solution: + def combinationSum(self, candidates: list[int], target: int) -> list[list[int]]: + candidates.sort() + result, path = [], [] + + def backtrack(start, remaining): + if remaining == 0: + result.append(path[:]) + return + for i in range(start, len(candidates)): + if candidates[i] > remaining: + break + + path.append(candidates[i]) + backtrack(i, remaining - candidates[i]) + path.pop() + + backtrack(0, target) + return result diff --git a/work/1/Medium/Array/46.permutations.py b/work/1/Medium/Array/46.permutations.py new file mode 100644 index 0000000..0483808 --- /dev/null +++ b/work/1/Medium/Array/46.permutations.py @@ -0,0 +1,59 @@ +""" +46. Permutations +Difficulty: Medium +https://leetcode.com/problems/permutations/ + +────────────────────────────────────────────────── + +Given an array nums of distinct integers, return all the possible +permutations. You can return the answer in any order. + + + +Example 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]] + +Example 2: + +Input: nums = [0,1] +Output: [[0,1],[1,0]] + +Example 3: + +Input: nums = [1] +Output: [[1]] + + + +Constraints: + + • 1 <= nums.length <= 6 + + • -10 <= nums[i] <= 10 + + • All the integers of nums are unique. +""" + + +class Solution: + def permute(self, nums: list[int]) -> list[list[int]]: + result, path = [], [] + used = [False] * len(nums) + + def backtrack(): + if len(path) == len(nums): + result.append(path[:]) + return + for i in range(len(nums)): + if used[i]: + continue + used[i] = True + path.append(nums[i]) + backtrack() + path.pop() + used[i] = False + + backtrack() + return result diff --git a/work/1/Medium/Array/78.subsets.py b/work/1/Medium/Array/78.subsets.py new file mode 100644 index 0000000..9872e53 --- /dev/null +++ b/work/1/Medium/Array/78.subsets.py @@ -0,0 +1,50 @@ +""" +78. Subsets +Difficulty: Medium +https://leetcode.com/problems/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. + + + +Example 1: + +Input: nums = [1,2,3] +Output: [[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]] + +Example 2: + +Input: nums = [0] +Output: [[],[0]] + + + +Constraints: + + • 1 <= nums.length <= 10 + + • -10 <= nums[i] <= 10 + + • All the numbers of nums are unique. +""" + + +class Solution: + def subsets(self, nums: list[int]) -> list[list[int]]: + res = [] + + def backtrack(i, path): + res.append(path[:]) + for num_idx in range(i, len(nums)): + path.append(nums[num_idx]) + backtrack(num_idx + 1, path) + path.pop() + + backtrack(0, []) + return res