递归代码模板
# 递归代码模板
def recursion(level, param1, param2, ...):
# recursion terminator
if level > MAX_LEVEL:
print_result
return
# process logic in current level
process_data(level, data...)
# drill down
self.recursion(level + 1, p1, ...)
# reverse the current level status if needed
reverse_status(level)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 回溯解题思路
用 for 循环去枚举出所有的选择。
做出一个选择。
基于这个选择,继续往下选择(递归)。
上面的递归结束了,撤销这个选择,进入 for 循环的下一次迭代。
# 括号生成
var generateParenthesis = function(n) {
const res = [];
const helper = (left, right, s) => {
if (left === n && right === n) {
res.push(s);
return;
}
// 如果左括号数量不大于 n,可以放一个左括号
if (left < n) {
helper(left + 1, right, s + "(");
}
// 如果右括号数量小于左括号的数量,可以放一个右括号
if (right < left) {
helper(left, right + 1, s + ")");
}
};
helper(0, 0, "");
return res;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var generateParenthesis = function(n) {
const res = [];
const helper = (left, right, s) => {
if (left === 0 && right === 0) {
res.push(s);
return;
}
if (left > 0) {
helper(left - 1, right, s + "(");
}
if (right > left) {
helper(left, right - 1, s + ")");
}
};
helper(n, n, "");
return res;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 子集 ✅
// 回溯:每个元素都有“选择”和“不选择”两种情况
// 时间复杂度 O(n * 2^n);空间复杂度 O(n),不计算返回结果占用的空间
var subsets = function(nums) {
// 保存所有子集
const res = [];
// i 表示当前准备处理的元素下标,arr 表示当前构建的子集
const helper = (i, arr) => {
// 所有元素都处理完成,得到一个完整子集
if (i === nums.length) {
// 必须保存副本,避免后续回溯修改 arr 时影响已经保存的结果
res.push(arr.slice());
return;
}
// 取当前的元素
arr.push(nums[i]);
helper(i + 1, arr);
// 撤销选择,恢复到处理当前元素之前的状态
arr.pop();
// 不取当前的元素
helper(i + 1, arr);
};
// 从下标 0 和空子集开始进行回溯
helper(0, []);
return res;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 二进制枚举:用 mask 的第 i 位表示是否选择 nums[i]
// 时间复杂度 O(n * 2^n);空间复杂度 O(n),不计算返回结果占用的空间
var subsets = function(nums) {
// 保存所有子集
const res = [];
const n = nums.length;
// n 个元素共有 2^n 种选择状态,mask 从 0 枚举到 2^n - 1
for (let mask = 0; mask < (1 << n); ++mask) {
// 保存当前 mask 对应的子集
const arr = [];
// 从低位到高位检查 mask 的每个二进制位,每一位对应 nums 中同下标的元素
for (let i = 0; i < n; ++i) {
// 1 << i:生成一个只有第 i 位为 1、其他位都为 0 的数字
// mask & (1 << i):通过按位与检查 mask 的第 i 位是否为 1
// 结果不为 0,说明第 i 位为 1,需要把 nums[i] 加入当前子集
// 例如 mask = 5(二进制 101)、i = 2 时,101 & 100 = 100,因此选择 nums[2]
if (mask & (1 << i)) {
arr.push(nums[i]);
}
}
// 当前子集已经构建完成,加入结果数组
res.push(arr);
}
return res;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 子集 II
var subsetsWithDup = function(nums) {
const res = [];
nums.sort();
const helper = (start, arr) => {
res.push(arr.slice());
if (start === nums.length) {
return;
}
for (let i = start; i < nums.length; i++) {
if (i > start && nums[i - 1] === nums[i]) {
continue;
}
arr.push(nums[i]);
helper(i + 1, arr);
arr.pop();
}
};
helper(0, []);
return res;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 组合
var combine = function(n, k) {
const res = [];
// start 是选择的起点,arr 是当前构建的组合
const helper = (start, arr) => {
if (arr.length === k) {
res.push([...arr]); // 将当前符合的组合推入结果中
return;
}
for (let i = start; i <= n; i++) {
// 遍历所有选择
arr.push(i); // 当前选择
helper(i + 1, arr); // 继续选择
arr.pop(); // 撤销当前选择
}
};
helper(1, []);
return res;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 组合总和
var combinationSum = function(candidates, target) {
const res = [];
const helper = (start, arr, sum) => {
if (sum >= target) {
if (sum === target) {
res.push(arr.slice());
}
return;
}
for (let i = start; i < candidates.length; i++) {
arr.push(candidates[i]);
helper(i, arr, sum + candidates[i]);
arr.pop();
}
};
helper(0, [], 0);
return res;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 组合总和 II
var combinationSum2 = function(candidates, target) {
const res = [];
candidates.sort();
const helper = (start, arr, sum) => {
if (sum >= target) {
if (sum === target) {
res.push(arr.slice());
}
return;
}
for (let i = start; i < candidates.length; i++) {
if (i > start && candidates[i - 1] === candidates[i]) {
continue;
}
arr.push(candidates[i]);
helper(i + 1, arr, sum + candidates[i]);
arr.pop();
}
};
helper(0, [], 0);
return res;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 组合总和 III
var combinationSum3 = function(k, n) {
const res = [];
const helper = (start, arr, sum) => {
if (arr.length === k) {
if (sum === n) {
res.push(arr.slice());
}
return;
}
for (let i = start; i <= 9; i++) {
arr.push(i);
helper(i + 1, arr, sum + i);
arr.pop();
}
};
helper(1, [], 0);
return res;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 全排列 ✅
// 回溯法:每一层枚举 “下一个可以放的数”,放完 n 个数就得到一个排列
// 时间复杂度 O(n × n!):一共有 n! 个排列,每个排列在 arr.slice() 拷贝时花费 O(n)
// 空间复杂度 O(n):不算 res 存储结果的开销,递归栈深度 + arr 长度都是 O(n)
var permute = function(nums) {
const res = []; // 收集所有排列结果
// arr:当前正在构造的排列(路径)
const helper = (arr) => {
if (arr.length === nums.length) {
// 路径长度等于 nums 长度,说明已经选完所有数,记录这一个排列
res.push(arr.slice()); // 用 slice 拷贝,避免后续回溯修改到 res 里保存的结果
return;
}
for (let i = 0; i < nums.length; i++) {
// arr.includes 用来判断 nums[i] 是否已经被选过,未选过才能继续选
if (!arr.includes(nums[i])) {
arr.push(nums[i]); // 做选择:把 nums[i] 加入当前路径
helper(arr); // 基于新路径继续往下选
arr.pop(); // 撤销选择(回溯),尝试路径的下一种可能
// 另一种写法:用 concat 生成新数组传入,不修改原 arr,天然不需要 pop 撤销
// 但 concat 每次都拷贝数组,时间和空间开销比 push/pop 版本更高
// helper(arr.concat(nums[i]));
}
}
};
helper([]); // 从空路径开始构造
return res;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 全排列 II
var permuteUnique = function(nums) {
const res = [];
const visited = [];
nums.sort();
const helper = (arr) => {
if (arr.length === nums.length) {
res.push(arr.slice());
return;
}
for (let i = 0; i < nums.length; i++) {
if (visited[i] || (i > 0 && nums[i - 1] === nums[i] && !visited[i - 1])) {
continue;
}
arr.push(nums[i]);
visited[i] = 1;
helper(arr);
arr.pop();
visited[i] = 0;
}
};
helper([]);
return res;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# N 皇后
const solveNQueens = (n) => {
const board = new Array(n);
for (let i = 0; i < n; i++) {
board[i] = new Array(n).fill("."); // 创建棋盘
}
const cols = new Set(); // 列
const leftDiag = new Set(); // 左对角线
const rightDiag = new Set(); // 右对角线
const res = [];
const helper = (row) => {
if (row === n) {
const stringsBoard = board.slice();
for (let i = 0; i < n; i++) {
stringsBoard[i] = stringsBoard[i].join("");
}
res.push(stringsBoard);
return;
}
for (let col = 0; col < n; col++) {
// 如果当前点的所在的列和左右对角线任意一处有皇后,那么跳过该点
if (cols.has(col) || leftDiag.has(row + col) || rightDiag.has(row - col))
continue;
// 放置皇后,并将对应的列和左右对角线记录下来
board[row][col] = "Q";
cols.add(col);
rightDiag.add(row - col);
leftDiag.add(row + col);
// 递归下一行
helper(row + 1);
// 恢复现场,撤销该点的皇后,并删掉对应的列和左右对角线记录
board[row][col] = ".";
cols.delete(col);
rightDiag.delete(row - col);
leftDiag.delete(row + col);
}
};
helper(0);
return res;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# N 皇后 II
const totalNQueens = (n) => {
const cols = new Set();
const leftDiag = new Set();
const rightDiag = new Set();
let count = 0;
const helper = (row) => {
if (row >= n) return count++;
for (let col = 0; col < n; col++) {
if (cols.has(col) || leftDiag.has(row + col) || rightDiag.has(row - col))
continue;
cols.add(col);
leftDiag.add(row + col);
rightDiag.add(row - col);
helper(row + 1);
cols.delete(col);
leftDiag.delete(row + col);
rightDiag.delete(row - col);
}
};
helper(0);
return count;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
cosnt totalNQueens = (n) => {
let count = 0;
const dfs = (row, col, leftDiag, rightDiag) => {
if (row >= n) {
count++;
return;
}
// 得到当前层所有的空位,即可以放置皇后的位置;同时将这些空位转成 1,已经放置了皇后的位置转成 0
let bits = (~(col | leftDiag | rightDiag)) & ((1 << n) - 1);
while (bits) {
const one = bits & -bits; // 得到最低位的 1
dfs(row + 1, col | one, (leftDiag | one) << 1, (rightDiag | one) >> 1); // 更新行、列和左右对角线,进行新一轮的遍历
bits = bits & (bits - 1); // 去掉最低位的 1
}
};
dfs(0, 0, 0, 0);
return count;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 单词搜索
var exist = function(board, word) {
const row = board.length;
const col = board[0].length;
const visited = new Array(row); // 与 board 一样大的二维数组,用于标记当前点是否访问过
for (let i = 0; i < row; i++) {
visited[i] = new Array(col);
}
const dfs = function(r, c, i) {
// r 是当前点的横坐标,c 是当前点的纵坐标,i 是当前检查的单词中的字符索引
if (i === word.length) {
// 递归出口,遍历完当前检查的单词
return true;
}
if (r < 0 || r >= row || c < 0 || c >= col) {
// 当前点坐标越界
return false;
}
if (visited[r][c] || board[r][c] !== word[i]) {
// 当前点已经访问过或者它不是目标点
return false;
}
visited[r][c] = true; // 标记当前点已被访问过
const next =
dfs(r - 1, c, i + 1) ||
dfs(r + 1, c, i + 1) ||
dfs(r, c - 1, i + 1) ||
dfs(r, c + 1, i + 1); // 检查当前点的上下左右四个点能否找到下一个字符
if (next) {
return true;
}
visited[r][c] = false; // 如果不能找到下一个字符,需要撤销当前点的访问状态
return false;
};
for (let i = 0; i < row; i++) {
for (let j = 0; j < col; j++) {
if (board[i][j] === word[0] && dfs(i, j, 0)) {
// 找到递归起点,并且递归结果为真
return true;
}
}
}
return false;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# 单词搜索 II
// 回溯 + 字典树
const findWords = (board, words) => {
const row = board.length;
const col = board[0].length;
const res = [];
// 构建字典树
const getTrie = (words) => {
let root = Object.create(null);
for (const word of words) {
let node = root;
for (let c of word) {
if (!node[c]) {
node[c] = Object.create(null);
}
node = node[c];
}
node.word = word;
}
return root;
};
const dfs = (r, c, trie) => {
if (trie.word) {
res.push(trie.word);
trie.word = null;
}
// 边界条件
if (r < 0 || r >= row || c < 0 || c >= col) return;
// 当前字符不在字典树中
if (!trie[board[r][c]]) return;
const temp = board[r][c];
board[r][c] = "#"; // 标记当前点已经访问过
dfs(r - 1, c, trie[temp]);
dfs(r + 1, c, trie[temp]);
dfs(r, c - 1, trie[temp]);
dfs(r, c + 1, trie[temp]);
board[r][c] = temp; // 遍历完当前点的上下左右 4 个点之后恢复现场
};
const trie = getTrie(words);
for (let i = 0; i < row; i++) {
for (let j = 0; j < col; j++) {
dfs(i, j, trie);
}
}
return res;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# 电话号码的字母组合
// DFS
var letterCombinations = function(digits) {
if (!digits.length) return [];
const res = [];
const map = {
"2": "abc",
"3": "def",
"4": "ghi",
"5": "jkl",
"6": "mno",
"7": "pqrs",
"8": "tuv",
"9": "wxyz"
};
const dfs = (cur, i) => {
// cur 是当前构建的字符串,i 是扫描的指针
if (i > digits.length - 1) {
// 指针越界,递归的出口
res.push(cur); // 将解推入结果中
return; // 结束当前递归分支,进入另一个递归分支
}
const letters = map[digits[i]]; // 当前数字对应哪些字母
for (const c of letters) {
// 不同的字母选择代表不同的递归分支
dfs(cur + c, i + 1); //递归
}
};
dfs("", 0);
return res;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// BFS
var letterCombinations = function(digits) {
if (!digits.length) return [];
const queue = [];
const map = {
"2": "abc",
"3": "def",
"4": "ghi",
"5": "jkl",
"6": "mno",
"7": "pqrs",
"8": "tuv",
"9": "wxyz"
};
queue.push("");
for (let i = 0; i < digits.length; i++) {
// bfs 的层数,即 digits 的长度
const count = queue.length; // 当前层的节点个数
for (let j = 0; j < count; j++) {
const cur = queue.shift(); // 让当前层的节点逐个出队
const letters = map[digits[i]];
for (const c of letters) {
queue.push(cur + c); // 生成新的字母串入列
}
}
}
return queue; // 队列中全是最后一层生成的字母串
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30