feat: 添加回溯算法17,39,77,46

This commit is contained in:
LouisFonda 2025-06-22 00:28:30 +08:00
parent 81d4ae749b
commit 2e1ef51465
Signed by: yigencong
GPG Key ID: 29CE877CED00E966
5 changed files with 183 additions and 0 deletions

3
backtrack/README.md Normal file
View File

@ -0,0 +1,3 @@
# 回溯算法
回溯算法就是“递归+循环+剪枝”是一种暴力搜索的方式适合解决组合排列分组SCP(例如n皇后数独等)
绘制决策树更易于我们理解收集路径和回溯的过程。

View File

@ -0,0 +1,54 @@
/**
* @param {string} digits
* @return {string[]}
* https://leetcode.cn/problems/letter-combinations-of-a-phone-number/
*/
const letterCombinations = function (digits) {
};
function f1(digits) {
// 特殊情况处理:如果输入是空字符串,直接返回空数组
if (!digits) return [];
// 数字到字母的映射表(与手机九宫格一致),下标 0 和 1 没有对应字母
const map = [
[], // 0
[], // 1
['a', 'b', 'c'], // 2
['d', 'e', 'f'], // 3
['g', 'h', 'i'], // 4
['j', 'k', 'l'], // 5
['m', 'n', 'o'], // 6
['p', 'q', 'r', 's'], // 7
['t', 'u', 'v'], // 8
['w', 'x', 'y', 'z'], // 9
];
const result = []; // 存放所有可能的组合结果
/**
* 回溯函数
* @param {string[]} path 当前递归路径字符数组
* @param {number} start 当前处理的是 digits 的第几个字符
*/
const backtrack = (path, start) => {
// 递归终止条件:如果 path 长度等于输入数字的长度,说明已经生成一个完整组合
if (start === digits.length) {
result.push(path.join('')); // 把字符数组转成字符串加入结果中
return;
}
// 获取当前数字对应的所有字母(注意把字符转成数字作为索引)
for (const letter of map[+digits[start]]) {
path.push(letter); // 做选择:添加一个字母
backtrack(path, start + 1); // 递归处理下一个数字
path.pop(); // 回溯:撤销上一步选择,尝试其他字母
}
};
// 从空路径、起始位置 0 开始回溯搜索
backtrack([], 0);
return result;
}

View File

@ -0,0 +1,8 @@
/**
* @param {number[]} candidates
* @param {number} target
* @return {number[][]}
*/
const combinationSum = function (candidates, target) {
};

View File

@ -0,0 +1,68 @@
/**
* @param {number} n
* @param {number} k
* @return {number[][]}
* https://leetcode.cn/problems/combinations/
*/
const combine = function (n, k) {
};
/*
利用回溯可以轻松的解决这个问题定义结果esult=[]用来收集结果定义backtrack(path, start)来收集结果和回溯
path用来收集路径
start: 用来标记选择组合元素的起始位置因为组合对顺序没有要求{12}{21}算一个组合避免重复
*/
function f1(n, k) {
const result = []; // 收集符合要求的组合
const backtrack = (path, start) => {
// 如果path的长度符合要求收集结果当path.length === k 时
if (path.length === k) {
result.push([...path]);
return; // 结束这个路径的收集
}
// 从start开始选择元素放入path进行组合
for (let i = start; i <= n; i++) {
// 将当前元素加入组合
path.push(i);
// 递归,组合新的元素
backtrack(path, i + 1);
// 回溯,将之前的组合路径去掉
path.pop();
}
};
backtrack([], 1);
return result;
}
/*
剪枝优化
*/
function f2(n, k) {
const result = []; // 收集符合要求的组合
const backtrack = (path, start) => {
if (path.length + (n - start + 1) < k) return; // 剪枝优化
// 如果path的长度符合要求收集结果当path.length === k 时
if (path.length === k) {
result.push([...path]);
return; // 结束这个路径的收集
}
// 从start开始选择元素放入path进行组合
for (let i = start; i <= n; i++) {
// 将当前元素加入组合
path.push(i);
// 递归,组合新的元素
backtrack(path, i + 1);
// 回溯,将之前的组合路径去掉
path.pop();
}
};
backtrack([], 1);
return result;
}

View File

@ -0,0 +1,50 @@
/**
* @param {number[]} nums
* @return {number[][]}
* http://leetcode.cn/problems/permutations/?envType=study-plan-v2&envId=top-interview-150
*/
const permute = function (nums) {
};
/*
利用回溯算法递归的处理每一次都从头开始遍历收集每一个元素由于是全排列不能再结果中收集同一个元素
多次所以定义一个used数组来标记是否已经收集如果收集过就跳过处理下一个元素如果path的长度和全排列的长度
一致就将结果收集到result中最后返回result
*/
function f2(nums) {
/**
*
* @param {Number[]} nums 所有元素
* @param {Boolean[]} used 对应位置的元素是否被使用过
* @param {Number[]} path 全排列的结果
*/
const backtrack = (nums, used, path) {
// 如果path的长度和nums的长度一致收集结果
if(path.length === nums.length){
result.push(path)
return
}
// 递归处理
for(let i = 0; i<nums.length;i++){
// 如果当前值被使用过,直接跳过
if(used[i]) continue
// 将当前值加入path
path.push(nums[i])
used[i] = true
backtrack(nums, used, path)
// 下面是回溯过程将nums[i]从path中取出,并标记为未被使用
path.pop();
used[i] = false
}
}
let result = []; // 收集所有全排列的结果
const n = nums.length;
const used = Array(n).fill(false);
const path = []; // 收集元素形成排列
// 调用回溯过程
backtrack(nums, used, path)
return result
}