sisterAn / JavaScript-Algorithms

基础理论+JS框架应用+实践,从0到1构建整个前端算法体系
5.51k stars 634 forks source link

百度&leetcode46:全排列问题 #102

Open sisterAn opened 4 years ago

sisterAn commented 4 years ago

给定一个 没有重复 数字的序列,返回其所有可能的全排列。

示例:

输入: [1,2,3]
输出:
[
  [1,2,3],
  [1,3,2],
  [2,1,3],
  [2,3,1],
  [3,1,2],
  [3,2,1]
]

附赠leetcode地址:leetcode

sisterAn commented 4 years ago

本题是回溯算法的经典应用场景

1. 算法策略

回溯算法是一种搜索法,试探法,它会在每一步做出选择,一旦发现这个选择无法得到期望结果,就回溯回去,重新做出选择。深度优先搜索利用的就是回溯算法思想。

2. 适用场景

回溯算法很简单,它就是不断的尝试,直到拿到解。它的这种算法思想,使它通常用于解决广度的搜索问题,即从一组可能的解中,选择一个满足要求的解。

3. 代码实现

我们可以写一下,数组 [1, 2, 3] 的全排列有:

即回溯的处理思想,有点类似枚举搜索。我们枚举所有的解,找到满足期望的解。为了有规律地枚举所有可能的解,避免遗漏和重复,我们把问题求解的过程分为多个阶段。每个阶段,我们都会面对一个岔路口,我们先随意选一条路走,当发现这条路走不通的时候(不符合期望的解),就回退到上一个岔路口,另选一种走法继续走。

0bf18f9b86a2542d1f6aa8db6cc45475fce5aa329a07ca02a9357c2ead81eec1-image 图片来自于:https://pic.leetcode-cn.com/0bf18f9b86a2542d1f6aa8db6cc45475fce5aa329a07ca02a9357c2ead81eec1-image.png

这显然是一个 递归 结构;

let permute = function(nums) {
    // 使用一个数组保存所有可能的全排列
    let res = []
    if (nums.length === 0) {
        return res
    }
    let used = {}, path = []
    dfs(nums, nums.length, 0, path, used, res)
    return res
}
let dfs = function(nums, len, depth, path, used, res) {
    // 所有数都填完了
    if (depth === len) {
        res.push([...path])
        return
    }
    for (let i = 0; i < len; i++) {
        if (!used[i]) {
            // 动态维护数组
            path.push(nums[i])
            used[i] = true
            // 继续递归填下一个数
            dfs(nums, len, depth + 1, path, used, res)
            // 撤销操作
            used[i] = false
            path.pop()
        }

    }
}

4. 复杂度分析

leetcode

Jeckhenry commented 4 years ago
const permMutation = function (arr) {
            let res = [];
            if (arr.length <= 1) {
                return arr;
            }
            for (let i = 0; i < arr.length; i++) {
                let indexStr = arr[i];
                let otherStr = [...arr.slice(0, i), ...arr.slice(i + 1)];
                let tmp = permMutation(otherStr);
                for (let j = 0; j < tmp.length; j++) {
                    res.push(Array.prototype.flat.call([indexStr, tmp[j]], Infinity));
                }
            }
            return res;
        }
marlboroKay commented 4 years ago

var permute = function(nums) {
  let len = nums.length;
  if(len === 0) return [[]];
  let res = [];
  let perm = function(arr, p, q, res){
    if(p === q){
      res.push([...arr])
    }
    for(let i = p; i <= q; i++){
      swap(arr, i, p);
      perm(arr, p+1, q, res);
      swap(arr, i, p);
    }
  }
  let swap = function(arr, left, right){
    let temp = arr[left];
    arr[left] = arr[right];
    arr[right] = temp;
  }
  perm(nums, 0, len-1, res)
  return res;
};
marlboroKay commented 4 years ago

题解 1.分析示列,不难发现全排列,首先将每个元素排到第一位。 2.剩余元素再重复第一步操作,依次处理各个元素。 3.通过1,2两步操作,可以利用递归实现操作,但是有个地方需要特别处理,就是对于移动的元素, 在递归操作之前,和之后该如何操作。 4.由此产生两种解决办法,一种是利用回溯,定义回溯状态标识,在递归操作完成后,重置状态 一种是利用swap函数(交换元素位置);相对来说,swap函数更好理解一些; 5.递归函数实现: 递归终止条件: 1.swap 子全排列的数组长度等于nums的长度时,递归终止。 2.dfs 子全排列所在层数(depth)等于nums的长度时,递归终止。

循环数组内部元素: 1.swap 通过swap,将nums[i] 与 p(从0开始)交换 调用递归函数prem,传入 nums, p+1, nums.lenght, res(结果集) 递归内部可以理解为,例如1,2,3 先将1取出,求解剩余2,3 的全排列,依次类推 当递归终止后,再次调用swap函数,将数组重置成原始状态;

2.dfs dfs函数中传入nums,len,depth(当前所在层数),path(存储当前排列结果的动态数组), used(元素是否已存在排列中的布尔标识), res(结果集) 调用递归dfs,判断当前元素是否在排列中used[i] === true ?,存在则跳过,不存在则 used[i] = true, 将当前nums[i] 存入path中,然后调用dfs dfs终止后,***重点来了,回溯状态需要重置,used[i] = false,path.pop() 将当前元素从已有排列中删除

全排列属于回溯入门问题,回溯还是需要多学多练

swap解法

var permute = function(nums) {
  let len = nums.length;
  if(len === 0) return [[]];
  let res = [];
  let perm = function(arr, p, q, res){
    if(p === q){
      res.push([...arr])
    }
    for(let i = p; i < q; i++){
      swap(arr, i, p);
      perm(arr, p+1, q, res);
      swap(arr, i, p);
    }
  }
  let swap = function(arr, left, right){
    let temp = arr[left];
    arr[left] = arr[right];
    arr[right] = temp;
  }
  perm(nums, 0, len, res)
  return res;
};

dfs解法


var permute = function(nums) {
  let len = nums.length;
  if(len === 0) return [[]];
  let res = [];
  let path = []; //维护动态数组
  let used = {}; //保存已存在的元素
  let dfs = function(arr, len, depth, path, used, res){
    if(len === depth){
      res.push([...path]);
      return
    }
    for(let i = 0; i < len; i++){
      if(!used[i]){
        path.push(arr[i]);
        used[i] = true;
        dfs(arr, len, depth+1, path, used, res)
        //状态回溯
        used[i] = false;
        path.pop();
      }
    }
  }
  dfs(nums, len, 0, path, used, res);
  return res;
}
DCbryant commented 4 years ago

const permute = nums => { const res = []; const len = nums.length; const curr = []; const use = {}; if (curr.length === len) { res.push(curr); }

function dfs (nth) { for (let i = 0; i < len; i++) { if (!use[i]) { use[i] = 1; curr.push(nums[i]); dfs(i); curr.pop(); use[i] = 0; } } }

dfs(0); return res; };

syc666 commented 3 years ago

感觉递归函数不需参数也可以

function fn(arr) {
    const path = [], used = {}
    const res = []
    const loopFn = () => {
        if (path.length === arr.length) {
            res.push([...path])
            return
        }
        for (let i = 0, len = arr.length; i < len; i++){
            if (!used[i]) {
                path.push(arr[i])
                used[i] = true
                loopFn()
                path.pop()
                used[i] = false
            }
        }
    }
    loopFn()
    return res
}
z253573760 commented 3 years ago
function bar(list) {
  const res = [];
  function dfs(arr = []) {
    if (arr.length === list.length) {
      res.push([...arr]);
      return;
    }
    for (const item of list) {
      if (arr.indexOf(item) === -1) {
        arr.push(item);
        dfs(arr);
        arr.pop();
      }
    }
  }
  dfs([]);
  return res;
}

const res = bar([1, 2, 3]);
console.log(res);
fengmiaosen commented 2 years ago
/**
 * 回溯算法
 * @param {number[]} nums 
 */
function permute(nums) {
    let res = []

    function dfs(track) {
        // 到达决策树底部
        if (track.length === nums.length) {
            res.push([...track])
            return
        }

        for (let num of nums) {
            // 剪枝操作,避免重复遍历
            if (track.includes(num)) {
                continue
            }

            track.push(num)

            dfs(track)

            track.pop()
        }
    }

    dfs([])

    return res
}

console.log(permute([1,2,3]));