回溯算法题目
回溯算法题目
1. 组合问题
1. 77. 组合
push进res的条件:path的长度符合要求
push进path的条件:存在于1到n之间的数
这是组合的基础问题
/**
* @param {number} n
* @param {number} k
* @return {number[][]}
*/
var combine = function(n, k) {
let res = [];
let path = [];
const backtracking = (start) => {
if(path.length === k) {
res.push([...path]);
return;
}
for(let i = start; i <= n; i++) {
path.push(i);
backtracking(i+1);
path.pop();
}
}
backtracking(1);
return res;
};
2. ⭐17. 电话号码的字母组合
这道题目的关键是在多个组合中选数字,相当于树的每一层的数是在不同的地方选出的。相当于是两个遍历,先遍历digits,在遍历每个digits里对应的数。
/**
* @param {string} digits
* @return {string[]}
*/
var letterCombinations = function(digits) {
const map = ["","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"];
let res = [];
let path = [];
if(!digits.length){
return res;
}
const backtracking = (start) => {
if(path.length === digits.length) {
res.push(path.join(""));
return;
}
for(let v of map[digits[start]]){
path.push(v);
backtracking(start+1);
path.pop()
}
}
backtracking(0);
return res;
};
3. 39. 组合总和
push进res的条件:path的和符合要求
push进path的条件:加上增加的数的和不超过target
由于组合的数是可以重复的,所有下一层的start是上一层刚被选上的数
/**
* @param {number[]} candidates
* @param {number} target
* @return {number[][]}
*/
var combinationSum = function(candidates, target) {
let res = [];
let path = [];
const backtracking = (pathSum,start) => {
if(pathSum === target) {
res.push([...path]);
return ;
}
for(let i = start; i< candidates.length;i++){
if(pathSum + candidates[i] > target) {
continue;
}
path.push(candidates[i]);
backtracking(pathSum + candidates[i],i);
path.pop();
}
}
backtracking(0,0);
return res;
};
4.40. 组合总和 II
⭐不能有重复的组合,也就是说树的每一层的数不能重复。解决方法:先给数组排序,使得一样的数聚集在一起,i>start
判断是否是第一个数,candidates[i] === candidates[i-1]
判断是否相等,不是第一个数且相等要跳过。
⭐每个数字只能用一次。解决方法:往下一层的时候从加入path的下一个数开始
/**
* @param {number[]} candidates
* @param {number} target
* @return {number[][]}
*/
var combinationSum2 = function(candidates, target) {
let res = [];
let path = [];
candidates.sort();
const backtracking = (start,pathSum) => {
if(pathSum === target) {
res.push([...path]);
return;
}
for(let i = start;i<candidates.length;i++) {
if(pathSum + candidates[i] > target || (i>start && candidates[i] === candidates[i-1])) {
continue;
}
path.push(candidates[i]);
backtracking(i+1,pathSum + candidates[i])
path.pop()
}
}
backtracking(0,0);
return res;
};
5. 216. 组合总和 III
push进res的条件:path的和等于target,path的长度等于k
push进path的条件:加上增加的数的和不超过target
组合的数是不可以重复,所有下一层的start是上一层的下一个
/**
* @param {number} k
* @param {number} n
* @return {number[][]}
*/
var combinationSum3 = function(k, n) {
let res = [];
let path = [];
const backtracking = (start,pathSum) => {
if(pathSum === n && path.length === k){
res.push([...path]);
return;
}
for(let i = start;i<=9;i++){
if(pathSum + i > n) {
continue;
}
path.push(i);
backtracking(i+1,pathSum+i);
path.pop();
}
}
backtracking(1,0);
return res;
};
2. 分割问题
1. 131. 分割回文串
重点是判断新切割的子串是否是回文串
/**
* @param {string} s
* @return {string[][]}
*/
var partition = function(s) {
let path = []
let res = []
const isP = (s,start,end)=>{
for(let i = start,j = end;i<j;i++,j--) {
if(s[i] !== s[j]){
return false;
}
}
return true
}
const backtracking = (s,start) => {
if(start >= s.length){
res.push([...path])
return;
}
for(let i = start; i< s.length;i++) {
if(!isP(s,start,i)) {
continue;
}
path.push(s.substr(start,i-start+1));
backtracking(s,i+1);
path.pop()
}
}
backtracking(s,0)
return res;
};
2. 93. 复原 IP 地址
push进res的条件:start到了最后,path的长度等于4
push进path的条件:字串不大于255,且前面第一位不能是0
这题的重点是+str
可以将字符串转换成可以跟数字作比较
/**
* @param {string} s
* @return {string[]}
*/
var restoreIpAddresses = function(s) {
let res = [];
let path = [];
const backtracking = (start) => {
if(start === s.length && path.length === 4) {
res.push(path.join('.'));
return;
}
if(path.length > 4) {
return;
}
for(let i = start; i < s.length; i++) {
let str = s.substr(start,i-start+1);
if(str.length > 1 && str[0] === "0"){
break;
}
if(str.length > 3 || +str > 255) {
break;
}
path.push(str);
backtracking(i+1);
path.pop();
}
}
backtracking(0);
return res;
};
3. 子集问题
1. 78. 子集
加入path条件:都可以
加入res条件:都可以
返回条件:start超出nums的长度范围
/**
* @param {number[]} nums
* @return {number[][]}
*/
var subsets = function(nums) {
let res = [];
let path = [];
const backtracking = (start,nums) => {
if (path.length <= nums.length){
res.push([...path]);
}else {
return;
}
for (let i = start; i < nums.length; i++) {
path.push(nums[i]);
backtracking(i+1,nums);
path.pop();
}
}
backtracking(0,nums)
return res;
};
2. 90. 子集 II
加入path条件:同一层中不同的数
加入res的条件:都可以
返回条件:start超出nums的范围
/**
* @param {number[]} nums
* @return {number[][]}
*/
var subsetsWithDup = function(nums) {
let res = [];
let path = [];
nums.sort();
const backtracking = (nums,start) => {
res.push([...path]);
if (start >= nums.length) {
return;
}
for (let i = start; i < nums.length; i++) {
if (i > start && nums[i] === nums[i-1]) {
continue;
}
path.push(nums[i]);
backtracking(nums,i+1);
path.pop();
}
}
backtracking(nums,0);
return res;
};
4. 排序问题
1. 46. 全排列
加入path条件:在path中没有的数(这里可以用findIndex,可以新开辟一个used数组存放是否使用过的数)
加入res的条件:path的程度跟nums的长度相等
返回条件:res,push完返回
/**
* @param {number[]} nums
* @return {number[][]}
*/
var permute = function(nums) {
let res = [];
let path = [];
const backtracking = (nums) => {
if (path.length === nums.length) {
res.push([...path]);
return;
}
for (let i = 0; i < nums.length; i++) {
if (path.findIndex((value => value === nums[i])) !== -1) {
continue;
}
path.push(nums[i]);
backtracking(nums);
path.pop();
}
}
backtracking(nums);
return res;
};
2. ⭐47. 全排列 II
这个加入的条件很特殊,需要判断同一层的相同的数是否已经加入过path,加入过就不加
⭐同一个数组中加入过的数不能再加,怎么判断是否加入过?用全局数组used,加入了就设置为true,没有加入设置为false
⭐同一层中不能加入一样的数,怎么去重?排序,判断(i>0 && nums[i-1] === nums[i] && !used[i-1])
要判断前面的数是否被用了,被用了代表虽然是相同的但是不在同一层,如果没有被用代表是在同一层
/**
* @param {number[]} nums
* @return {number[][]}
*/
var permuteUnique = function(nums) {
let res = [];
let path = [];
nums.sort();
let used = new Array(nums.length).fill(false);
const backtracking = (nums) => {
if (path.length === nums.length) {
res.push([...path]);
return;
}
for (let i = 0; i < nums.length; i++) {
// 如果这个数用过就跳过
if (used[i]){
continue;
}
// 如果这个是跟前面的一个数相同而且前面的一个数没用过
if (i>0 && nums[i-1] === nums[i] && !used[i-1]){
continue;
}
path.push(nums[i]);
used[i] = true;
backtracking(nums);
path.pop();
used[i] = false;
}
}
backtracking(nums);
return res;
};
5. 棋盘问题
1. 51. N 皇后
思路:以每一行为单位,看一个每一行的那个位置可以走,就放在那个位置,如果有一行的全部位置都不可以走,那么就回到上一行,移动上一行的棋子到别的地方,直到所有的棋子都放完
/**
* @param {number} n
* @return {string[][]}
*/
var solveNQueens = function(n) {
function isValid(row, col, chessBoard, 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(chessBoard) {
let chessBoardBack = [];
chessBoard.forEach(row => {
let rowStr = '';
row.forEach(value => {
rowStr += value;
})
chessBoardBack.push(rowStr)
})
return chessBoardBack
}
let chessBoard = Array.from(new Array(n),()=>new Array(n).fill('.'));
let res = [];
const backtracking = (row,chessBoard) => {
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)
chessBoard[row][col] = '.'
}
}
}
backtracking(0,chessBoard);
return res
};
2. 37. 解数独
跟上一题是一样的,我记得这题在大一学java的时候做过,那时候没学算法,写了一整天都没写出来
/**
* @param {character[][]} board
* @return {void} Do not return anything, modify board in-place instead.
*/
var solveSudoku = function(board) {
function isValid(row, col, val, board) {
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) * 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
}
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,String(k),board)) {
board[i][j] = String(k);
if (backtracking()){
return true
}
board[i][j] = '.'
}
}
return false
}
}
return true
}
backtracking();
return board
};
6. 总结
-
归纳一个回溯的模板
function backtracking() { if(终止条件){ 收集结果 return } for(集合元素){ // 处理节点看能不能放进path backtracking() // 回溯(撤销操作) } }
-
回溯需要确定的三个问题:怎样可以放进path,怎样可以放进res,什么时候返回