代码随想录Day29 | 46. 全排列、47. 全排列 III、491. 递增子序列 | 回溯

94 阅读1分钟

46. 全排列

题目链接:46. 全排列

思路: 全排列和子集/组合问题的区别在于,对于子集/组合问题来说,start向前移动之后,start之前的内容不会再用到,但是全排列会用到。需要额外使用 used 数组来标记哪些元素还可以被选择。used 数组标记已经在路径上的元素避免重复选择,然后收集所有叶子节点上的值,就是所有全排列的结果。

class Solution {

    LinkedList<Integer> track = new LinkedList<>();
    List<List<Integer>> res = new LinkedList<>();
    boolean[] used;

    public List<List<Integer>> permute(int[] nums) {
        used = new boolean[nums.length];
        backtrack(nums);
        return res;
    }

    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;
            }
            track.add(nums[i]);
            used[i] = true;
            backtrack(nums);
            track.removeLast();
            used[i] = false;
        }
    }
}

47. 全排列 II

题目链接:47. 全排列 II

思路: 保证相同元素在排列中的相对位置保持不变。比如说 nums = [1,2,2'] 这个例子,保持排列中 2 一直在 2' 前面。

   class Solution {

    List<List<Integer>> res = new LinkedList<>();
    // 记录回溯算法的递归路径
    LinkedList<Integer> track = new LinkedList<>();
    // track 中的元素会被标记为 true
    boolean[] used;

    public List<List<Integer>> permuteUnique(int[] nums) {
        Arrays.sort(nums);
        used = new boolean[nums.length];
        backtrack(nums);
        return res;
    }

    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 - 1] == nums[i] && !used[i - 1]) {
                continue;
            }
            track.add(nums[i]);
            used[i] = true;
            backtrack(nums);
            track.removeLast();
            used[i] = false;
        }
    }
}

491. 递增子序列

题目链接:491. 递增子序列

思路: 这题看上去很像子集问题,但是有很大区别。在子集问题中,我们通过排序数组再加一个标记数组来来避免重复。但对于这题,如果先排列数组,那么数组直接就变成递增的了。通过画树可以看出,同一父节点下的同层上使用过的元素就不能在使用了,因此可以使用一个set来存储已经用过的数字。注意在收集结果的位置,不要return,因为要取树上的所有节点。

class Solution {

    LinkedList<Integer> track = new LinkedList<>();
    List<List<Integer>> res = new LinkedList<>();

    public List<List<Integer>> findSubsequences(int[] nums) {
        backtrack(nums, 0);
        return res;
    }

    void backtrack(int[] nums, int start) {
        if (track.size() >= 2) {
            res.add(new LinkedList<>(track));
            // 注意这里不要加return,因为要取树上的所有节点
        }
        HashSet<Integer> used = new HashSet<>();
        for (int i = start; i < nums.length; i++) {
            if (!track.isEmpty() && track.getLast() > nums[i]) {
                continue;
            }
            if (used.contains(nums[i])) {
                continue;
            }
            used.add(nums[i]);
            track.add(nums[i]);
            backtrack(nums, i + 1);
            track.removeLast();
        }
    }
}