Compare commits

...
15 changed files with 778 additions and 2 deletions

No files matched your search

@@ -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'
---
<Badge variant="accent">Array</Badge>
### 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
```
@@ -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'
---
<Badge variant="accent">Array</Badge>
### 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
```
@@ -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'
---
<Badge variant="accent">Array</Badge>
### 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
```
+163
View File
@@ -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.
+3
View File
@@ -0,0 +1,3 @@
import { defineMeta } from "blume";
export default defineMeta({ title: "Backtracking", order: 20 });
@@ -0,0 +1,58 @@
---
title: '323. Number of Connected Components in an Undirected Graph'
description: You have a graph of n nodes. You are given an integer n and an array edges where edges[i] = [ai, bi] indicates that there is an edge between ai and bi in the graph
sidebar:
label: 'Number of Connected Components in an Undirected Graph'
badge: 'Medium'
---
<Badge variant="accent">Depth-First Search</Badge>
### Example 1:
- Input: `n = 5, edges = [[0,1],[1,2],[3,4]]`
- Output: `2`
### Example 2:
- Input: `n = 5, edges = [[0,1],[1,2],[2,3],[3,4]]`
- Output: `1`
### Constraints:
- `1 <= n <= 2000`
- `1 <= edges.length <= 5000`
- `edges[i]` = [ai, bi]
- `ai != bi`
- There are no repeated edges.
## Solution
```py
class Solution:
def countComponents(self, n: int, edges: list[list[int]]) -> int:
par = [i for i in range(n + 1)]
rank = [1] * (n + 1)
def find(n):
res = n
while res != par[res]:
par[res] = par[par[res]]
res = par[res]
return res
def union(n1, n2):
p1, p2 = find(n1), find(n2)
if p1 == p2:
return 0
if rank[p1] > rank[p2]:
par[p2] = p1
rank[p1] += rank[p2]
else:
par[p1] = p2
rank[p2] += rank[p1]
return 1
res = n
for n1, n2 in edges:
res -= union(n1, n2)
return res
```
@@ -0,0 +1,60 @@
---
title: '684. Redundant Connection'
description: In this problem, a tree is an undirected graph that is connected and has no cycles
sidebar:
label: 'Redundant Connection'
badge: 'Medium'
---
<Badge variant="accent">Depth-First Search</Badge>
### Example 1:
- Input: `edges = [[1,2],[1,3],[2,3]]`
- Output: `[2,3]`
### Example 2:
- Input: `edges = [[1,2],[2,3],[3,4],[1,4],[1,5]]`
- Output: `[1,4]`
### Constraints:
- `n == edges.length`
- `3 <= n <= 1000`
- `edges[i].length == 2`
- `1 <= ai < bi <= edges.length`
- `ai != bi`
- There are no repeated edges.
- The given graph is connected.
## Solution
```py
class Solution:
def findRedundantConnection(self, edges: list[list[int]]) -> list[int]:
N = len(edges)
par = [i for i in range(N + 1)]
rank = [1] * (N + 1)
def find(n):
if n != par[n]:
par[n] = find(par[n])
return par[n]
def union(n1, n2):
p1, p2 = find(n1), find(n2)
if p1 == p2:
return False
if rank[p1] > rank[p2]:
par[p2] = p1
rank[p1] += rank[p2]
else:
par[p1] = p2
rank[p2] += rank[p1]
return True
for n1, n2 in edges:
if not union(n1, n2):
return [n1, n2]
```
+9
View File
@@ -0,0 +1,9 @@
---
title: Union Find
description: 'Union-Find (Disjoint Set Union) is a data structure that maintains a collection of disjoint sets and supports efficiently finding which set an element belongs to and merging two sets.'
hidden: true
---
+3
View File
@@ -0,0 +1,3 @@
import { defineMeta } from "blume";
export default defineMeta({ title: "Union-Find", order: 19 });
+2 -2
View File
@@ -176,8 +176,8 @@ const topicRoutes = (nav: unknown): Record<string, string> => {
// ── component ────────────────────────────────────────────────────
interface ViewBox { x: number; y: number; w: number; h: number }
/** The resting view — 2× on the centre so node labels read at the content width; 1× stays reachable by zooming out. */
const HOME_ZOOM = 2;
/** The resting view — 1.6× on the centre so node labels read at the content width; 1× stays reachable by zooming out. */
const HOME_ZOOM = 1.6;
const HOME: ViewBox = { x: (W - W / HOME_ZOOM) / 2, y: (H - H / HOME_ZOOM) / 2, w: W / HOME_ZOOM, h: H / HOME_ZOOM };
const clamp = (v: number, lo: number, hi: number) => Math.min(Math.max(v, lo), hi);
+75
View File
@@ -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
+59
View File
@@ -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
+50
View File
@@ -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
@@ -0,0 +1,69 @@
"""
323. Number of Connected Components in an Undirected Graph
Difficulty: Medium
https://leetcode.com/problems/number-of-connected-components-in-an-undirected-graph/
──────────────────────────────────────────────────
You have a graph of n nodes. You are given an integer n and an array
edges where edges[i] = [ai, bi] indicates that there is an edge
between ai and bi in the graph.
Return the number of connected components in the graph.
Example 1:
Input: n = 5, edges = [[0,1],[1,2],[3,4]]
Output: 2
Example 2:
Input: n = 5, edges = [[0,1],[1,2],[2,3],[3,4]]
Output: 1
Constraints:
• 1 <= n <= 2000
• 1 <= edges.length <= 5000
• edges[i] = [ai, bi]
• ai != bi
• There are no repeated edges.
"""
class Solution:
def countComponents(self, n: int, edges: list[list[int]]) -> int:
par = [i for i in range(n + 1)]
rank = [1] * (n + 1)
def find(n):
res = n
while res != par[res]:
par[res] = par[par[res]]
res = par[res]
return res
def union(n1, n2):
p1, p2 = find(n1), find(n2)
if p1 == p2:
return 0
if rank[p1] > rank[p2]:
par[p2] = p1
rank[p1] += rank[p2]
else:
par[p1] = p2
rank[p2] += rank[p1]
return 1
res = n
for n1, n2 in edges:
res -= union(n1, n2)
return res
@@ -0,0 +1,81 @@
"""
684. Redundant Connection
Difficulty: Medium
https://leetcode.com/problems/redundant-connection/
──────────────────────────────────────────────────
In this problem, a tree is an undirected graph that is connected and
has no cycles.
You are given a graph that started as a tree with n nodes labeled
from 1 to n, with one additional edge added. The added edge has two
different vertices chosen from 1 to n, and was not an edge that
already existed. The graph is represented as an array edges of length
n where edges[i] = [ai, bi] indicates that there is an edge between
nodes ai and bi in the graph.
Return an edge that can be removed so that the resulting graph is a
tree of n nodes. If there are multiple answers, return the answer that
occurs last in the input.
Example 1:
Input: edges = [[1,2],[1,3],[2,3]]
Output: [2,3]
Example 2:
Input: edges = [[1,2],[2,3],[3,4],[1,4],[1,5]]
Output: [1,4]
Constraints:
• n == edges.length
• 3 <= n <= 1000
• edges[i].length == 2
• 1 <= ai < bi <= edges.length
• ai != bi
• There are no repeated edges.
• The given graph is connected.
"""
class Solution:
def findRedundantConnection(self, edges: list[list[int]]) -> list[int]:
N = len(edges)
par = [i for i in range(N + 1)]
rank = [1] * (N + 1)
def find(n):
if n != par[n]:
par[n] = find(par[n])
return par[n]
def union(n1, n2):
p1, p2 = find(n1), find(n2)
if p1 == p2:
return False
if rank[p1] > rank[p2]:
par[p2] = p1
rank[p1] += rank[p2]
else:
par[p1] = p2
rank[p2] += rank[p1]
return True
for n1, n2 in edges:
if not union(n1, n2):
return [n1, n2]