一、回溯算法介绍
  1. 什么是回溯算法
    回溯算法也可以回溯搜索法,是一种搜索的方式。
    回溯是递归的副产品,只要有递归就会有回溯
  2. 回溯算法模板
    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
}