回溯算法解析-以Leetcode46为例

59 阅读2分钟

1. 回溯算法的基本思想

回溯算法,简单来说就是就是解决一颗决策树,这里拿leetcode 46 全排列问题为例子: 该问题是需要我们返回一个无重复数字的数组的全排列。解决这个问题,我们回想中学时候的排列组合,例如我们需要对[1, 2, 3]进行全排列,我们会首先选择1,然后对23排列,然后是选2开始,选3开始诸如此类。如果画成图示,如下:

image.png

2. 回溯算法的细节

上面这幅图就是我们手动操作排列组合的框架,而回溯算法就是在这个树状图的基础上加上了选择,和选择回退,使用递归进行整个树状图的遍历从而得到结果。 比如,参考下图,我们现在站在1这个节点,我们之前已经选择过了1,我们剩下的选择是2和3,我们可以做出任一选择,直到我们的选择列表为空,于是我们回撤一个选择,继续递归的过程。

image.png

那我们现在开始来做leetcode 46,首先明确我们需要知道

  1. 我们已经做出的选择,也就是路径
  2. 接下来可以做的选择,选择列表
  3. 回撤一个选择,结束条件
/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var permute = function(nums) {

};

第一步: 定义我们的结果res数组,定义我们的路径track数组,定义我们的used数组

这里我们使用used数组,去排除我们已经做出的选择

var permute = function(nums) {
    let res = []
    let track = []
    let used = new Array(nums.length).fill(false)
};

第二步:定义我们的traverse函数,接收track, nums,used数组;

var permute = function(nums) {
    let res = []
    let track = []
    let used = new Array(nums.length).fill(false)
    
    function backTraverse(nums, track, used) {
        // 如前述,我们需要判断结束条件
        if (nums.length === track.length) {
            res.push(track.slice())
            return;
        }
        // 遍历树
        for (let i = 0; i < nums.length; i++) {
             if (used[i]) {
                 continue;
             }
             // 做选择
             track.push(nums[i])
             used[i] = true
             // 递归
             backTraverse(nums, track, used)
             // 回撤
             track.pop()
             used[i] = false
        }
    }
    backTraverse(nums, track, used)
    return res
};