全排列 II(力扣第47题)

题目:给定一个可包含重复数字的序列,返回所有不重复的全排列。

示例:

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

分析:

  这个题和全排列一的不同之处在于,它给定的一组元素含有重复的元素存在,那么我们进行深度递归搜索的时候,选择下一步搜索的元素的时候就要修改原先再全排列一中的判断条件了。那么满足什么条件,才能作为当前元素的下一步搜索选择呢?

  首先我们从最开始进行分析,给定一组带有重复的元素,一个含有n个,最开始的可选的元素个数为去重之后的元素个数,比如[1,1,2],那么开始时,可选的元素个数为2个,即1和2,假设选定1作为其实搜索元素,那么继续向下搜索,从1走向下一步,我们再来选择,要求必须是未被访问的,且去重之后的,此时可选的范围还是两个,即1和2,假设选定1;继续搜索,只有2了,那就选定2。此时得到一种排列组合:1,1,2

  总结一下,在这个题目中,选择作为下一步搜索元素的条件有两个:

    (1)此元素未被访问过;

    (2)此元素只能在候选范围中出现一次;

  那么如何解决这个去重问题呢,我的想法是借助哈希表,在遍历当前元素的所有的可走的下一步元素时,先判断此元素在哈希表中是否存在,如果存在,说明其已经在候选范围中出现了,那么就跳过,否则就可以作为候选,走向这个元素进行下一步搜索。

  注意,我说的这个候选范围是指,对于当前正在遍历的元素来说,如果岂不是最后一个点,其必然要向下进行搜索,那么向下搜索的时候最少有一条路可走,而我们要对这些可走的路根据题意设定条件进行筛选,全排列一给定的一组元素是不重复的,所以只要这组元素中有未被访问的点那么就能入选向下搜索的候选元素,但是现在有了重复元素,所以对于当前元素来说,其向下搜索时不设定去重条件,那么其向下搜索的候选元素就会有重复的元素,那么最终也会造成元素排列的重复,所以我们要进行去重,去重的方法就是利用哈希表,每次向下搜索前先判断这个元素是否在哈希表中存在,如果存在那么就说明候选元素已经有此元素了,并且已经可能产生排列结果了,当前这个就没有必要继续访问了,直接跳过即可。

 

    private List<List<Integer>> reslist;
    public List<List<Integer>> permuteUnique(int[] nums) {

        if (nums.length == 0 || nums == null){
            return new ArrayList<>();
        }
        int n = nums.length;
        boolean[] isVisited = new boolean[n];
        reslist = new ArrayList<List<Integer>>();
        List<Integer> sortres = new ArrayList<>();
        findAllSort(nums,isVisited,sortres);


        return reslist;
    }

    private void findAllSort(int[] nums,boolean[] isVisited, List<Integer> sortres) {

        if (sortres.size() == nums.length){
            reslist.add(new ArrayList<>(sortres));
            return;
        }

        HashMap<Integer, Integer> isSame = new HashMap<>();

        for (int i = 0; i < isVisited.length; i++) {

            if (isVisited[i] || isSame.get(nums[i])!=null){
                continue;
            }

            isSame.put(nums[i],1);
            isVisited[i] = true;

            sortres.add(nums[i]);
            findAllSort(nums,isVisited,sortres);
            sortres.remove(sortres.size()-1);
            isVisited[i] = false;
        }

    }

  我的程序运行的时间效率一般,只超过了百分之四十多的人,所以学习借鉴一下cyc2018大神的代码,它判断当前要向下搜索的元素是否是重复的,是先进行了排序,然后判断这个元素是否等于排序后的数组中的前一个元素,如果等于并且前一个元素还未被访问,那么就跳过这个元素,否则可以访问。他的程序运行效率很高,超过了百分之百的人。

    List<List<Integer>> permutes = new ArrayList<>();
    List<Integer> permuteList = new ArrayList<>();
    Arrays.sort(nums);  // 排序
    boolean[] hasVisited = new boolean[nums.length];
    backtracking(permuteList, permutes, hasVisited, nums);
    return permutes;
}

private void backtracking(List<Integer> permuteList, List<List<Integer>> permutes, boolean[] visited, final int[] nums) {
    if (permuteList.size() == nums.length) {
        permutes.add(new ArrayList<>(permuteList));
        return;
    }

    for (int i = 0; i < visited.length; i++) {
        if (i != 0 && nums[i] == nums[i - 1] && !visited[i - 1]) {
            continue;  // 防止重复
        }
        if (visited[i]){
            continue;
        }
        visited[i] = true;
        permuteList.add(nums[i]);
        backtracking(permuteList, permutes, visited, nums);
        permuteList.remove(permuteList.size() - 1);
        visited[i] = false;
    }
}

 

posted @ 2020-07-06 09:38  有心有梦  阅读(163)  评论(0编辑  收藏  举报