组合问题:N个数里面按一定规则找出k个数的集合
切割问题:一个字符串按一定规则有几种切割方式
子集问题:一个N个数的集合里有多少符合条件的子集
排列问题:N个数按一定规则全排列,有几种排列方式
棋盘问题:N皇后,解数独等等
void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
}
var combine = function(n, k) {
let res = [], path = [];
const backtracking = (startIndex) => {
if (path.length === k) {
res.push([...path]);
return;
}
for (let i = startIndex; i <= n; i++) {
path.push(i);
backtracking(i + 1);
path.pop();
}
}
backtracking(1);
return res;
};
接下来看一下优化过程如下:
已经选择的元素个数:path.size();
还需要的元素个数为: k - path.size();
在集合n中至多要从该起始位置 : n - (k - path.size()) + 1,开始遍历
为什么有个+1呢,因为包括起始位置,我们要是一个左闭的集合。
举个例子,n = 4,k = 3, 目前已经选取的元素为0(path.size为0),n - (k - 0) + 1 即 4 - ( 3 - 0) + 1 = 2。
从2开始搜索都是合理的,可以是组合[2, 3, 4]。
for (let i = startIndex; i <= n - (k - path.length) + 1; i++)
var combinationSum3 = function(k, n) {
let res = [], path = [], sum = 0;
const backtracking = (index) => {
if (sum > n) return;
if (path.length === k) {
if (sum === n) res.push([...path]);
return;
}
for (let i = index; i <= 9 - (k - path.length) + 1; i++) {
path.push(i);
sum += i;
backtracking(i + 1);
sum -= i;
path.pop();
}
}
backtracking(1);
return res;
};
var letterCombinations = function(digits) {
const n = digits.length;
const map = ["","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"];
let res = [], path = [];
if (!n) {
return [];
}
const backtracking = (index) => {
if (path.length === n) {
res.push(path.join(''));
return;
}
let str = map[digits[index]];
for (let i = 0; i < str.length; i++) {
path.push(str[i]);
backtracking(index + 1);
path.pop();
}
}
backtracking(0);
return res;
};
如果是一个集合来求组合的话,就需要startIndex,例如:77.组合 (opens new window),216.组合总和III (opens new window)。
如果是多个集合取组合,各个集合之间相互不影响,那么就不用startIndex,例如:17.电话号码的字母组合
var combinationSum = function(candidates, target) {
const n = candidates.length;
let res = [], path = [], sum = 0;
const backtracking = (index) => {
if (sum > target) return;
else if (sum === target) {
res.push([...path]);
return;
}
for (let i = index; i < n && sum + candidates[i] <= target; i++) {
path.push(candidates[i]);
sum += candidates[i];
backtracking(i); // 关键点:不用i+1了,表示可以重复读取当前的数
sum -= candidates[i];
path.pop();
}
}
backtracking(0);
return res;
};
sort() 方法用原地算法对数组的元素进行排序,并返回数组。默认排序顺序是在将元素转换为字符串,然后比较它们的 UTF-16 代码单元值序列时构建的
const numbers = [2, 1, 3];
numbers.sort(function (a, b) {
return a - b;
});
console.log(numbers); // [1,2,3]
numbers.sort(function (a, b) {
return b - a;
})
console.log(numbers); // [3,2,1]
var combinationSum2 = function(candidates, target) {
const n = candidates.length;
let res = [], path = [], sum = 0;
candidates.sort((a,b) => a - b);
const backtracking = (index) => {
if (sum === target) {
res.push([...path]);
return;
}
for (let i = index; i < n && sum + candidates[i] <= target; i++) {
if (i > index && candidates[i] === candidates[i - 1]) {
continue;
}
path.push(candidates[i]);
sum += candidates[i];
backtracking(i + 1);
sum -= candidates[i];
path.pop();
}
}
backtracking(0);
return res;
};
const isPalindrome = (s, l, r) => {
for (let i = l, j = r; i < j; i++, j--) {
if (s[i] !== s[j]) return false;
}
return true;
}
var partition = function (s) {
let n = s.length;
let res = [], path = [];
const backtracking = (i) => {
if (i >= n) {
res.push([...path]);
return;
}
for (let j = i; j < n; j++) {
let str = s.slice(i, j + 1);
if (!isPalindrome(s, i, j)) continue;
path.push(str);
backtracking(j + 1);
path.pop();
}
}
backtracking(0);
return res;
};
var restoreIpAddresses = function(s) {
let res = [], path = [];
const backtracking = (i) => {
const len = path.length;
if (len > 4) return;
// 当path有四段,而且使用了全部字符
if (len === 4 && i === s.length) {
res.push(path.join('.'));
return;
}
for (let j = i; j < s.length; j++) {
const str = s.slice(i, j + 1);
if (str.length > 3 || +str > 255) break;
if (str.length > 1 && str[0] === '0') break;
path.push(str);
backtracking(j + 1);
path.pop();
}
}
backtracking(0);
return res;
};
var subsets = function(nums) {
const n = nums.length;
let res = [], path = [];
const backtracking = (i) => {
res.push([...path]);
for (let j = i; j < n; j++) {
path.push(nums[j]);
backtracking(j + 1);
path.pop();
}
}
backtracking(0);
return res;
};
var subsetsWithDup = function(nums) {
let res = [], path = [];
nums.sort((a, b) => a - b);
const backtracking = (i) => {
res.push([...path]);
for (let j = i; j < nums.length; j++) {
// 同树层,相同元素只用一次
if (j > i && nums[j] === nums[j - 1]) continue;
path.push(nums[j]);
backtracking(j + 1);
path.pop();
}
}
backtracking(0);
return res;
};
var findSubsequences = function(nums) {
let res = [], path = [];
const backtracking = (i) => {
if (path.length >= 2) {
res.push([...path]);
}
let set = [];
for (let j = i; j < nums.length; j++) {
if (path.length && nums[j] < path[path.length - 1] || set[nums[j] + 100]) {
continue;
}
set[nums[j] + 100] = true; // -100 ~ 100
path.push(nums[j]);
backtracking(j + 1);
path.pop();
}
}
backtracking(0);
return res;
};
var permute = function (nums) {
let res = [], path = [], used = [];
const n = nums.length;
const backtracking = () => {
if (path.length === n) {
res.push([...path]);
return;
}
for (let i = 0; i < n; i++) {
if (used[i]) {
continue;
}
path.push(nums[i]);
used[i] = true;
backtracking();
used[i] = false;
path.pop();
}
}
backtracking();
return res;
};
var permuteUnique = function(nums) {
const n = nums.length;
let res = [], path = [], used = [];
const backtracking = () => {
if (path.length === n) {
res.push([...path]);
return;
}
let set = [];
for (let i = 0; i < n; i++) {
if (used[i] || set[nums[i] + 10]) continue;
path.push(nums[i]);
used[i] = true;
set[nums[i] + 10] = true;
backtracking();
path.pop();
used[i] = false;
}
}
backtracking();
return res;
};
var solveNQueens = function (n) {
function isValid(row, col, cheesBorad, n) {
for (let i = 0; i < row; i++) {
if (chessBoard[i][col] === 'Q') return false;
}
for (let i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {
if (chessBoard[i][j] === 'Q') return false;
}
for (let i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) {
if (chessBoard[i][j] === 'Q') return false;
}
return true;
}
function transformChessBoard(arr) {
let chessBoard = [];
arr.forEach((row) => {
let s = "";
row.forEach((item) => {
s += item;
})
chessBoard.push(s);
})
return chessBoard;
}
let res = [];
let chessBoard = new Array(n).fill().map(() => new Array(n).fill('.'));
const backtracking = (row) => {
if (row === n) {
res.push(transformChessBoard(chessBoard));
return;
}
for (let col = 0; col < n; col++) {
if (isValid(row, col, chessBoard, n)) {
chessBoard[row][col] = 'Q';
backtracking(row + 1);
chessBoard[row][col] = '.';
}
}
}
backtracking(0);
return res;
};
/**
* @param {character[][]} board
* @return {void} Do not return anything, modify board in-place instead.
*/
var solveSudoku = function(board) {
const isValid = (row, col, val) => {
for (let i = 0; i < board.length; i++) {
if (board[i][col] === val) return false;
}
for (let i = 0; i < board.length; i++) {
if (board[row][i] === val) return false;
}
let startRow = Math.floor(row / 3) * 3;
let startCol = Math.floor(col / 3) * 3;
for (let i = startRow; i < startRow + 3; i++) {
for (let j = startCol; j < startCol + 3; j++) {
if (board[i][j] === val) return false;
}
}
return true;
}
const 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 val = 1; val <= 9; val++) {
if (isValid(i, j, val.toString())) {
board[i][j] = val.toString();
if (backtracking()) return true;
board[i][j] = '.';
}
}
return false;
}
}
return true;
}
backtracking();
return board;
};