• LeetCode:全排列II【47】


    LeetCode:全排列II【47】

    参考自天码营题解:https://www.tianmaying.com/tutorial/LC47

    题目描述

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

    示例:

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

    题目分析

      这道题与上一道全排列I的区别在于,这一次给的序列可以包含重复元素。

      1、那此时我们怎么判断当前元素是否使用过呢?

        我们使用BitMap(位图)技术建立一个和序列长度相等的布尔数组,记录每一个位置的元素是否使用过就可以了。这样,无疑会遍历到每一个不同的排列,但是也存在着问题,以样例为例,[1,1,2]这样的排列会被遍历到许多次,这就导致最后输出的答案存在着重复

      2、如何解决这样的重复呢?

        我们先看看这样的重复是如何产生的,不难发现,[1,1,2]在之前描述的回溯中会枚举2次。如果不用数值而是用这个数在nums中的下标来表示的话,[1,1,2]被枚举的两次分别是[0,1,2]和[1,0,2],即下标为0的“1”和下标为1的“1”在枚举的过程中被考虑成了两个不同的选择,但是在最后的答案中却没有什么不同。

        而这样产生的重复也非常好解决,就是对于一个位置的同一个取值,只枚举一次,也就是如果已经在第1个位置上枚举了“1”这个数字,那么即使之后仍然有“1”的取值,也都跳过不进行枚举

          在实际的实现中,我们不妨这样枚举,即将nums数组排序后,只有nums[i]不等于nums[i-1]时,才将nums[i]视作一种可能的取值,即:

    for (int i = 0; i < nums.size(); i++) {
        // 确保在一个位置不会枚举两个相同的数
        if (i == nums.size() - 1 || nums[i] != nums[i-1]) {
    
        }
    }
    

        这样,就可以保证[1,1,2]在最终的方案中只被计算一次,即同一个取值的元素只取最左边的

        特别的,当加入了used[]数组用于判断一个数字是否被使用过之后,由于每次使用的一定是所有相同数字中最左侧的一个,所以对于一个取值,如果它左侧的数字是已经被使用过了的,就同样说明这个数是当前所有相同数字中最左侧的可用的了,即:

    for (int i = 0; i < nums.size(); i++) if (!used[i]) {
        // 确保在一个位置不会枚举两个相同的数
        if (i == nums.size() - 1 || nums[i] != nums[i - 1] || used[i - 1]) {
    
        }
    }

      我的解法比较简单,没有利用排序,也没有跳过策略,只是在最后放入结果集的时候,检测是否有排列的元素出现过,这将会导致很多无意义的递归过程。

    高效解法

    public List<List<Integer>> permuteUnique(int[] nums) {
        List<List<Integer>> list = new ArrayList<>();
        Arrays.sort(nums);
        backtrack(list, new ArrayList<>(), nums, new boolean[nums.length]);
        return list;
    }
    
    private void backtrack(List<List<Integer>> list, List<Integer> tempList, int [] nums, boolean [] used){
        if(tempList.size() == nums.length){
            list.add(new ArrayList<>(tempList));
        } else{
            for(int i = 0; i < nums.length; i++){
                if(used[i] || i > 0 && nums[i] == nums[i-1] && !used[i - 1]) continue;
                used[i] = true; 
                tempList.add(nums[i]);
                backtrack(list, tempList, nums, used);
                used[i] = false; 
                tempList.remove(tempList.size() - 1);
            }
        }
    }

    Java题解

    class Solution {
        public List<List<Integer>> permuteUnique(int[] nums) {
            List<List<Integer>> lists = new ArrayList<>();
            backtrack(lists,new ArrayList<>(),new boolean[nums.length],nums);
            return lists;
        }
    
        public  void backtrack(List<List<Integer>> lists,List<Integer> tmpList,boolean[] visited,int[] nums)
        {
            if(tmpList.size()==nums.length&&!lists.contains(tmpList))
                lists.add(new ArrayList<>(tmpList));
            else
                for(int i=0;i<nums.length;i++)
                {
                    if(visited[i])
                        continue;
                    visited[i]=true;
                    tmpList.add(nums[i]);
                    backtrack(lists,tmpList,visited, nums);
                    visited[i]=false;
                    tmpList.remove(tmpList.size()-1);
                }
        }
    } 
  • 相关阅读:
    《Effective Java》 读书笔记(三) 使用私有构造方法或枚举实现单例类
    《Effective Java》 读书笔记(二) 在构造参数过多的时候优先考虑使用构造器
    读书笔记-《Maven实战》-2018/5/3
    读书笔记-《Maven实战》-关于Maven依赖传递的思考 2018/4/26
    MySQL基础篇(07):用户和权限管理,日志体系简介
    SpringCloud微服务:Sentinel哨兵组件,管理服务限流和降级
    MySQL基础篇(06):事务管理,锁机制案例详解
    Java并发编程(02):线程核心机制,基础概念扩展
    SpringBoot2 整合ElasticJob框架,定制化管理流程
    Java基础篇(02):特殊的String类,和相关扩展API
  • 原文地址:https://www.cnblogs.com/MrSaver/p/9938724.html
Copyright © 2020-2023  润新知