全排列 - 三种形式

全排列 - 三种形式

思路 - 回溯

  1. 「路径」,记录已经做过的选择
  2. 「选择列表- 多叉树」,表示当前可以做出的选择,在前序和后序位置操作。
    1. 前序位置,做选择
    2. 进入下一层决策树
    3. 后序位置,撤销选择
  3. 「结束条件」 遍历到树的底层叶子节点

46. 全排列 - 元素无重不可复选

给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。

题解

public List<List<Integer>> res = new LinkedList<>(); // 记录结果
public LinkedList<Integer> track = new LinkedList<>(); // 记录路径
public List<List<Integer>> permute(int[] nums) {
    backtrack(nums);
    return res;
}
// 回溯
public void backtrack(int[] nums) {
    // 结束条件,base case,到达叶子节点
    if (track.size() == nums.length) {
        // 找出一个全排列,退出。不能直接add(track),track引用的对象一直在变化,最后track为空,导致res添加的所有track也全都为空。
        res.add(new LinkedList<>(track));
        return;
    }
    // 选择列表
    for (int i = 0; i < nums.length; i++) {
        // 已存在的路径排除,避免重复使用
        if (track.contains(nums[i])) continue;
        track.add(nums[i]); // 前序位置,做选择
        backtrack(nums); // 进入下一层决策树
        track.removeLast(); // 后序位置,撤销选择
    }
}

避免重复选择

  1. 使用集合的 contains() 方法判断
  2. 使用 used数组 标记还可以被选择的元素

变种:求元素个数为 k 的排列

// 求元素个数为 k 的排列
public void backtrack(int[] nums, int k) {
    if (track.size() == k) {
        res.add(new LinkedList<>(track));
        return;
    }
    for (int i = 0; i < nums.length; i++) {
        if (track.contains(nums[i])) continue;
        track.add(nums[i]);
        backtrack(nums, k);
        track.removeLast();
    }
}

47. 全排列 II - 元素可重不可复选

给定一个可包含重复数字的序列 nums按任意顺序 返回所有不重复的全排列。

题解

方法一:保证相同元素在排列中的相对位置保持不变

public List<List<Integer>> res = new LinkedList<>();
public LinkedList<Integer> track = new LinkedList<>();
public boolean[] used;
public List<List<Integer>> permuteUnique(int[] nums) {
    // 先排序,让相同元素靠在一起
    Arrays.sort(nums);
    used = new boolean[nums.length];
    backtrack(nums);
    return res;
}
// 剪枝,相同元素在排列中的相对位置保持不变
public void backtrack(int[] nums) {
    if (track.size() == nums.length) {
        res.add(new LinkedList<>(track));
        return;
    }
    for (int i = 0; i < nums.length; i++) {
        if (used[i]) continue; // 剪枝,已访问过,跳出
        if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) continue; // 剪枝,没有访问过的树枝,若值相同,跳过(相对位置改变导致重复啦,要跳过)
        track.add(nums[i]);
        used[i] = true;
        backtrack(nums);
        track.removeLast();
        used[i] = false;
    }
}

方法二:记录前一条树枝的值,相同的只遍历第一个树枝,其它跳过

public void backtrack2(int[] nums) {
    if (track.size() == nums.length) {
        res.add(new LinkedList<>(track));
        return;
    }
    // 记录前一条树枝的值,初始化为特殊值
    int prevNum = -110;
    for (int i = 0; i < nums.length; i++) {
        if (used[i]) continue; // 剪枝,已访问过,跳出
        if (prevNum == nums[i]) continue; // 剪枝,相同则跳过,去重复
        track.add(nums[i]);
        used[i] = true;
        prevNum = nums[i]; // 记录这条树枝上的值
        backtrack2(nums);
        track.removeLast();
        used[i] = false;
    }
}

方法三:set去重

// 方法三 ,找出全排列, 然后通过set去重,效率低
// public Set> set = new HashSet<>();

全排列 III - 元素无重可复选

允许重复使用元素,去除剪枝逻辑即可。

public List<List<Integer>> res = new LinkedList<>(); // 结果
public LinkedList<Integer> track = new LinkedList<>(); // list记录路径
public List<List<Integer>> permute(int[] nums) {
    backtrack(nums);
    return res;
}
/**
 * 回溯
 */
public void backtrack(int[] nums) {
    // base case,到达叶子节点
    if (track.size() == nums.length) {
        // 收集叶子节点上的值
        res.add(new LinkedList<>(track));
        return;
    }
    for (int i = 0; i < nums.length; i++) {
        track.add(nums[i]); // 做选择
        backtrack(nums); // 进入下一层决策树
        track.removeLast(); // 撤销选择
    }
}

你可能感兴趣的:(算法,算法,全排列)