diff --git a/backtrack/README.md b/backtrack/README.md new file mode 100644 index 0000000..5d4f5de --- /dev/null +++ b/backtrack/README.md @@ -0,0 +1,3 @@ +# 回溯算法 +回溯算法就是“递归+循环+剪枝”,是一种暴力搜索的方式,适合解决组合,排列,分组,SCP(例如:n皇后,数独等), +绘制决策树更易于我们理解收集路径和回溯的过程。 diff --git a/backtrack/combinations/17电话号码.js b/backtrack/combinations/17电话号码.js new file mode 100644 index 0000000..189c56d --- /dev/null +++ b/backtrack/combinations/17电话号码.js @@ -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; +} diff --git a/backtrack/combinations/39组合总和.js b/backtrack/combinations/39组合总和.js new file mode 100644 index 0000000..eeb4245 --- /dev/null +++ b/backtrack/combinations/39组合总和.js @@ -0,0 +1,8 @@ +/** + * @param {number[]} candidates + * @param {number} target + * @return {number[][]} + */ +const combinationSum = function (candidates, target) { + +}; diff --git a/backtrack/combinations/77组合.js b/backtrack/combinations/77组合.js new file mode 100644 index 0000000..91ad015 --- /dev/null +++ b/backtrack/combinations/77组合.js @@ -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: 用来标记选择组合元素的起始位置,因为组合对顺序没有要求{1,2},{2,1}算一个组合,避免重复 +*/ +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; +} diff --git a/top-interview-leetcode150/backtrack/46全排列.js b/top-interview-leetcode150/backtrack/46全排列.js new file mode 100644 index 0000000..1b48ad8 --- /dev/null +++ b/top-interview-leetcode150/backtrack/46全排列.js @@ -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