- 22. 括号生成">22. 括号生成
- 40. 组合总和 II">40. 组合总和 II
- 216. 组合总和 III">216. 组合总和 III
- 46. Permutations">46. Permutations
- 47. Permutations II">47. Permutations II
- 78. 子集">78. 子集
- 90. 子集 II">90. 子集 II
- 491. 递增子序列">491. 递增子序列
- 18. 四数之和(和491相同的去重思路)">18. 四数之和(和491相同的去重思路)
- 51. N-Queens">51. N-Queens
- 37. 解数独">37. 解数独
- 39. Combination Sum">39. Combination Sum
- 45. 跳跃游戏 II">45. 跳跃游戏 II
22. 括号生成
这个的回溯技法 和 46 一样给了我很大的震撼。
var generateParenthesis = function(n) {let ans = [];function dfs(leftP, rightP, strPath) {if (strPath.length === 2 * n) {ans.push(strPath);}if (leftP > 0) {dfs(leftP - 1, rightP, `${strPath}(`)}if (leftP < rightP) {dfs(leftP, rightP - 1, `${strPath})`)}}dfs(n, n, '');return ans;};
40. 组合总和 II
组合很简单,但是去重逻辑还是使用昨天和 491 以及 18
var combinationSum2 = function(candidates, target) {let n = candidates.length;if (n === 0) {return;}candidates.sort((a, b) => a - b);let ans = [];function dfs(path, start) {let sum = path.reduce((pre, cur) => pre + cur, 0);if (sum === target) {ans.push(path);}if (sum >= target) {return;}let used = new Set();for (let i = start; i < n; i++) {if (used.has(candidates[i])) {continue;}used.add(candidates[i]);dfs([...path, candidates[i]], i + 1);}}dfs([], 0);return ans;};
216. 组合总和 III
var combinationSum3 = function(k, n) { // 老方法let ans = [];function dfs(path, start) {if (path.length === k) {let sum = path.reduce((pre, cur) => pre + cur, 0);if (sum === n) {ans.push(path);}return;}for (let i = start; i <= 9; i++) {dfs([...path, i], i + 1);}}dfs([], 1);return ans;};
46. Permutations
在微软的面试中,一点都没写出来,奇耻大辱啊。
var permute = function(nums) {let n = nums.length;if (n <= 1) {return [[nums]]}let ans = [];let used = new Array(n).fill(false); // 只借助一个多余数组就可以完成function dfs(path) { // 本来是一个无数次的 for 循环,被轻松破解if (path.length === n) { // 人为去找终止条件!!!ans.push(path);return;}for (let i = 0; i < n; i++) {if (used[i]) {continue;}used[i] = true; // 仍然是前序操作dfs([...path, nums[i]]);used[i] = false; // 回溯}}dfs([]);return ans;};
47. Permutations II
加点料
var permuteUnique = function(nums) {let n = nums.length;if (n <= 1) {return [[nums]];}nums.sort((a, b) => a - b);let ans = [];let used = new Array(n).fill(false); // 通用状态记录function dfs(path) {if (path.length === n) { // 人为去找终止条件!!!ans.push(path);return;}let pre;for (let i = 0; i < n; i++) {if (used[i]) { // 状态1: 是否被祖先节点使用过continue;}if (nums[i] === pre) { // 状态2:是否与兄弟节点的值相同continue;}pre = nums[i];if (!used[i]) {used[i] = true;dfs([...path, nums[i]]);used[i] = false;}// 不认真思考,很容易写成下面的样子// if (nums[i] !== pre) {// pre = nums[i];// if (!used[i]) {// used[i] = true;// dfs([...path, nums[i]]);// used[i] = false;// }// } else {// continue;// }}}dfs([]);return ans;};
78. 子集
以下这个太复杂:(包含了使用set去重的步骤)
/*** @param {number[]} nums* @return {number[][]}*//*** @param {number[]} nums* @return {number[][]}*/var subsets = function(nums) {let n = nums.length;if (n <= 0) {return [];}let used = new Array(n).fill(false);let ans_set = new Set(); // 使用set去重function dfs(path) {for (let i = 0; i < n; i++) {if (used[i]) {continue;}used[i] = true;let childPath = [...path, nums[i]].sort();ans_set.add(childPath.join(','));dfs(childPath);used[i] = false;}}dfs([]);let result = Array.from(ans_set).map(item => item.split(','));result.push([]);return result;};
简单些:(把去重一并做了)
var subsets = function(nums) {let n = nums.length;if (n <= 0) {return [];}let ans = [];function dfs(path, start) {ans.push(path);for (let i = start; i < n; i++) {dfs([...path, nums[i]], i + 1); // 注意这里没有回溯的步骤}}dfs([], 0);return ans;};
把path写开,就有回溯了:
var subsets = function(nums) {let n = nums.length;if (n <= 0) {return [];}let ans = [];let path = [];function dfs(start) {ans.push(path.slice(0));for (let i = start; i < n; i++) {path.push(nums[i]);dfs(i + 1);path.pop(); // 有了回溯}}dfs(0);return ans;};
90. 子集 II
和 47 排列问题一样的手段,要去重,先排序。
var subsetsWithDup = function(nums) {let n = nums.length;nums.sort((a, b) => a - b); // 先排序去对付这种去重的问题let ans_set = new Set();function dfs(path, start) {ans_set.add(path);let pre;for (let i = start; i < n; i++) {if (pre !== nums[i]) {pre = nums[i];} else {continue;}dfs([...path, nums[i]], i + 1);}}dfs([], 0);return Array.from(ans_set);};
491. 递增子序列
重点在于去重,看这样一个例子:
数组:x, y, 1(a), z,1(b), 1(c) , 有3个相同的1,但是用括号 a b c 标明三个1 在 不同位置上。我们要从其中挑出两个元素,要求值不能重复:1(a), 1(b) 和 1(b) ,1(c) 是重复的
去重原理:
上图红色框到的两个部分就是相同的!
看代码实现:
var findSubsequences = function(nums) {let n = nums.length;if (n <= 1) {return [];}let ans = [];// let used = [];// function dfs(path, start) {// if (path.length > 1) {// ans.push(path);// }// let pre; // 只能检测相邻相等元素// for (let i = start; i < n; i++) {// if (pre === nums[i]) {// continue;// }// pre = nums[i];// used[nums[i]] = true;// let last = path.length ? path[path.length - 1] : -Infinity;// if (last <= nums[i]) {// dfs([...path, nums[i]], i + 1);// }// }// }function dfs(path, start) {if (path.length >= 2) {ans.push(path.slice(0));}let used = new Set(); // 能检测相邻或不相邻的 相等元素for (let i = start; i < n; i++) {if (used.has(nums[i])) {continue;}used.add(nums[i]);let last = (path.length > 0) ? path[path.length - 1] : -Infinity;if (last <= nums[i]) {dfs([...path, nums[i]], i + 1);}}}dfs([], 0);return ans;};
18. 四数之和(和491相同的去重思路)
/*** @param {number[]} nums* @param {number} target* @return {number[][]}*/var fourSum = function(nums, target) {nums.sort((a, b) => a - b);let ans = [];function dfs(path, start) {if (path.length === 4) {let sum = path.reduce((pre, cur) => pre + cur);if (sum === target) {ans.push(path);}return sum;}let used = new Set();for (let i = start; i < nums.length; i++) {if (used.has(nums[i])) {continue;}used.add(nums[i]);let sum = dfs([...path, nums[i]], i + 1);if (sum !== undefined && sum >= target) { // addbreak;}}}dfs([], 0);return ans;};
除了四数之和,还能解决 三数之和。
小结:在求数组排列时,我们知道如何使用递归的方式去做,这里也是同样的要求选择三个数,道理一样。但是盲目递归是暴力解法,对于本题的条件
我们先对数组进行排列,将会大大减少时间复杂度。
51. N-Queens
var solveNQueens = function(n) {if (n <= 1) {return [[nums]]}let ans = [];let cols = new Set(); // used1let dias1 = new Set(); // used2let dias2 = new Set(); // used3function dfs(row) {if (row === n) { // 人为去找终止条件!!!let tmp = [];for (let col of cols) {let _tmp = new Array(n).fill('.');_tmp[col] = 'Q';tmp.push(_tmp);}ans.push(tmp);return;}for (let i = 0; i < n; i++) {if (cols.has(i)) {continue;}if (dias1.has(i - row)) {continue;}if (dias2.has(i + row)) {continue;}cols.add(i); // 仍然是前序操作dias1.add(i - row);dias2.add(i + row);dfs(row + 1);cols.delete(i)dias1.delete(i - row);dias2.delete(i + row);}}dfs(0);return ans;};
这里提出一个性概念,对于矩阵有斜列:
根据这样的新分法,矩阵里的内容可以重新被划分一次。非常巧妙。
37. 解数独
解题思路
亮点,不断从头重试
递归函数 用 两个 for 循环 嵌套 查询 列与行 的 格子,如果
不为空格
不为空,有两种情况,1)棋盘初始值,2)棋盘的尝试值 (其实第二种情况在递归里被统一了)
啥都不做,继续看下一个格子为空格
每一个 空格试 1-9个数字,
1)如果9 个数字都不满足,则需要回溯;
2)如果有数字 i 满足,则将该空格,记录为 i, 再从头重试。这里填入的值,构成的 新的初始棋盘 再去求解。
何时是边界:
- 递归到 棋盘的最后一个空格,这时 试 1-9个数字,可以选出符合条件的数字,找到了本题的解。
- 如果 最后一个空格 也是 棋盘最后一行最后一列的位置,这时 棋盘全有值,构成的 新的初始棋盘 再去求解,全部通过,返回 true,无需回溯修改。
var solveSudoku = function(board) {function isValid(row, col, val) {let len = board.length// 行不能重复for(let i = 0; i < len; i++) {if(board[row][i] == val) {return false}}// 列不能重复for(let i = 0; i < len; i++) {if(board[i][col] == val) {return false}}let startRow = Math.floor(row / 3) * 3let startCol = Math.floor(col / 3) * 3for(let i = startRow; i < startRow + 3; i++) {for(let j = startCol; j < startCol + 3; j++) {if(board[i][j] == val) {return false}}}return true}function backTracking() {for(let i = 0; i < board.length; i++) {for(let j = 0; j < board[0].length; j++) {if(board[i][j] !== '.') continue;for(let k = 1; k <= 9; k++) {if (isValid(i, j, k)) {board[i][j] = String(k);if (backTracking()) return true;board[i][j] = `.`}}return false}}return true;}backTracking()};
39. Combination Sum
这道题我没有使用回溯,但是使用两处剪枝。
var combinationSum = function(candidates, target) {let ans = new Set();function dfs(cans, tar, path) {cans = cans.filter(can => can <= tar); // 剪枝 1if (cans.length === 0) { // 人为去找终止条件!!!let sum = path.reduce((pre, cur) => pre + cur, 0);if (sum === target) {let tmp = path.sort((a, b) => a - b).join(',');ans.add(tmp);}return;}for (let num of cans) {dfs(cans, tar - num, [...path, num]); // 剪枝 2}}dfs(candidates, target, []);return Array.from(ans).map(item => item.split(','))};
在去重的效率非常低:
之后看看别人是怎么写的,目前我对这类题的认知只有这么高了。
45. 跳跃游戏 II
这种技法可以解决很多问题,超乎想象
var jump = function(nums) {let ans = [];function dfs(start, depth) {if (start >= nums.length - 1) {ans.push(depth);return;}let steps = nums[start];for (let i = 1; i <= steps; i++) {dfs(start + i, depth + 1);}}dfs(0, 0);return Math.min(...ans);};
