一、回溯算法介绍
- 什么是回溯算法
回溯算法也可以回溯搜索法,是一种搜索的方式。
回溯是递归的副产品,只要有递归就会有回溯 - 回溯算法模板
1) 回溯函数模板返回值及参数
2)回溯函数终止条件
3)回溯的遍历过程
var result = [], path = []
// 回溯函数的参数一般有多个
function backtracking(a, b, ...){
if(/*终止条件*/){
// 存放结果
result.push([...path])
return
}
for(/*选择: 本层集合中元素*/){
// 处理条件
backtracking(a, b, ..)
//回溯
// 撤销处理结果
}
}
二、Letcode39 组合总和(JavaScript 写法)
题目: 给定一个无重复元素的正整数数组 candidates 和一个正整数 target ,找出 candidates 中所有可以使数字和为目标数 target 的唯一组合。
candidates 中的数字可以无限制重复被选取。如果至少一个所选数字数量不同,则两种组合是唯一的。
对于给定的输入,保证和为 target 的唯一组合数少于 150 个。
var combinationSum = function(candidates, target) {
// 第一种写法,比较繁琐
// var res = []
// function comb(target, arr) {
// for(var i = 0; i < candidates.length; i++){
// if(target >= candidates[i]){
// var arr_2 = [...arr]
// arr_2.push(candidates[i])
// comb(target - candidates[i], arr_2)
// }
// }
// if(target == 0) {
// res.push([...arr])
// }
// return
// }
// comb(target, [])
// for(var i = 0; i < res.length; i++){
// res[i].sort((a,b) => a - b);
// res[i] = res[i].join("-");
// }
// res = [...new Set(res)];
// for(var i = 0; i < res.length; i++){
// res[i] = res[i].split("-");
// }
// return res
var res = [], path = []
function backtracking(target, sum, startIdx){
if(sum > target) return
if(target == sum) {
res.push([...path])
return
}
for(var i = startIdx; i < candidates.length; i++){
sum += candidates[i]
path.push(candidates[i])
backtracking(target, sum, i)
//回溯
sum -= candidates[i]
path.pop()
}
}
backtracking(target, 0, 0)
return res
};
三、Letcode40 组合总和 Ⅱ(JavaScript 写法)
题目:给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。
/**
* @param {number[]} nums
* @return {number[][]}
*/
var permuteUnique = function(nums) {
nums = nums.sort((a, b) => a-b)
var res = [], path = [], used = {}
function backtracking(arr) {
if(arr.length == 0) {
res.push([...path])
return
}
for(var i = 0; i < arr.length; i++){
if(i> 0 && arr[i] == arr[i-1]){
continue
}
path.push(arr[i])
var val = arr.splice(i, 1)[0]
backtracking(arr)
path.pop()
arr.splice(i, 0, val)
}
}
backtracking(nums)
return res
};
四、Letcode46 全排列
题目: 给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
/**
* @param {number[]} nums
* @return {number[][]}
*/
var permute = function(nums) {
var res = [], path = []
function backtracking(arr){
if(arr.length == 0){
res.push([...path])
return
}
for(var i = 0; i < arr.length; i++){
path.push(arr[i])
var val = arr.splice(i, 1)[0]
backtracking(arr)
path.pop()
arr.splice(i, 0, val)
}
}
backtracking(nums)
return res
};
五、letCode77 组合
题目:给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。
/**
* @param {number} n
* @param {number} k
* @return {number[][]}
*/
var combine = function(n, k) {
var res = [], path = []
function combNum(s){
// 终止条件
if(path.length == k){
res.push([...path])
return
}
//遍历
for(var i = s; i <= n; i++){
path.push(i)
combNum(i + 1)
// 回溯
path.pop()
}
}
combNum(1)
return res
};
六、letCode78 子集
题目:给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
/**
* @param {number[]} nums
* @return {number[][]}
*/
var subsets = function(nums) {
var len = nums.length - 1
var res = [[]], path = []
function subNum(startIdx){
if(startIdx > len) return
for(var i = startIdx; i <= len; i++){
path.push(nums[i])
res.push([...path])
subNum(i + 1)
path.pop()
}
}
subNum(0)
return res
};
七、letCode79 单词搜索
题目:给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
/**
* @param {character[][]} board
* @param {string} word
* @return {boolean}
*/
var exist = function(board, word) {
if(board == null) return false
var m = board.length, n = board[0].length
var path = []
for(let i = 0; i < m; i++){
path[i] = []
}
var find = false
/*
* path 记录当前点位是否已经被访问
* pos: 记录目标单词的字符索引,只有棋盘格字符和pos指向的字符一致时,才有机会继续搜索接下来的字符;如果pos已经过了目标单词的尾部了,那么便说明找到目标单词了
*/
function existStr(i, j, pos){
if(i < 0 || i >= m || j < 0 || j >= n || path[i][j] || find || board[i][j] !== word[pos]){
return
}
if(pos == word.length - 1){
find = true
return
}
path[i][j] = true // 表示已访问当前节点
existStr(i - 1, j, pos + 1) // 遍历当前节点的上下左右
existStr(i + 1, j, pos + 1)
existStr(i, j + 1, pos + 1)
existStr(i, j - 1, pos + 1)
path[i][j] = false // 回溯
}
for(let i = 0; i < m; i++){
for(let j = 0; j < n; j++){
existStr(i, j, 0)
}
}
return find
};
八、letCode90 子集Ⅱ
题目:给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
/**
* @param {number[]} nums
* @return {number[][]}
*/
var subsetsWithDup = function(nums) {
nums = nums.sort((a, b) => a-b)
var res = [[]], path = []
var strArr = []
function subNum(startIdx){
if(startIdx >= nums.length) return
for(var i = startIdx; i < nums.length; i++){
path.push(nums[i])
var str = [...path].join('')
if(!strArr.includes(str)) {
res.push([...path])
strArr.push(str)
}
subNum(i+1)
path.pop()
}
}
subNum(0)
return res
};
九、letCdoe131 分割回文串
题目:给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。
回文串 是正着读和反着读都一样的字符串。
/**
* @param {string} s
* @return {string[][]}
*/
var partition = function(s) {
var list = []
function dfs(s, start, path){
if(start == s.length) {
list.push([...path])
return
}
for(let i = start; i < s.length; i++){
let s1 = s.slice(start, i + 1)
if(!isPalindrome(s1)) continue
path.push(s1)
dfs(s, i+1, path, list)
path.pop()
}
}
dfs(s, 0, [])
return list
};
function isPalindrome(s){
if(s.length <= 1) return true
let left = 0, right = s.length-1;
while(left < right){
if(s[left] != s[right]) return false
left++
right--
}
return true
}