前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >LeetCode 46 & 47. Permutations I&II

LeetCode 46 & 47. Permutations I&II

原创
作者头像
大学里的混子
修改于 2019-04-17 08:48:32
修改于 2019-04-17 08:48:32
79400
代码可运行
举报
文章被收录于专栏:LeetCodeLeetCode
运行总次数:0
代码可运行

本文讲述的是有关于排列问题,这也是算法中的一个重要的方法:回溯法。

46. Permutations

Given a collection of distinct integers, return all possible permutations.

Example:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Input: [1,2,3]
Output:
[
  [1,2,3],
  [1,3,2],
  [2,1,3],
  [2,3,1],
  [3,1,2],
  [3,2,1]
]

题目大意:给出一个数组,这个数组是没有重复数字的,求出这个数组的全排列。

解法:

思路:这个其实就是一个树形问题。树形问题采用回溯法是较为经典的方法。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    List<List<Integer>> res = new ArrayList<>();
    boolean[] used;
    public List<List<Integer>> permute(int[] nums) {
        if (nums == null || nums.length == 0) return res;
        LinkedList<Integer> p = new LinkedList<>();
        used = new boolean[nums.length];
        permuteHelper(nums,0,p);
        return res;
     }

     public void permuteHelper(int[] nums,int index,LinkedList<Integer> list){
        if (nums.length == index) {
            LinkedList<Integer> listClone = (LinkedList<Integer>)list.clone();
            res.add(listClone);
            return;
        }

        for (int i = 0 ;i< nums.length;i++){
            if(used[i] == false){
                list.addLast(nums[i]);
                used[i] = true;
                permuteHelper(nums,index+1,list);
                used[i] = false;
                list.removeLast();
            }
        }
     }

我个人是不喜欢采用全局变量的,所以还是该为了函数变量。代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    public List<List<Integer>> permute(int[] nums) {
        List<List<Integer>> permuteRes = new ArrayList<>();
        boolean[] used = new boolean[nums.length];
        if (nums == null || nums.length == 0) return permuteRes;
        LinkedList<Integer> p = new LinkedList<>();
        permuteHelper(nums,p,permuteRes,used);
        return permuteRes;
    }

    public void permuteHelper(int[] nums,LinkedList<Integer> list, List<List<Integer>> permuteRes, boolean[] used){
        if (nums.length == list.size()) {
            LinkedList<Integer> listClone = (LinkedList<Integer>)list.clone();
            permuteRes.add(listClone);
            return;
        }
        for (int i = 0 ;i<nums.length;i++){
            if(used[i] == false){
                list.addLast(nums[i]);
                used[i] = true;
                permuteHelper(nums,list,permuteRes,used);
                used[i] = false;
                list.removeLast();
            }
        }
    }

这是一个最为基本的排列问题,下面我们来看看一个有重复数字的数组,看看是如何让进行排列的。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    List<List<Integer>> res = new ArrayList<>();
    boolean[] used;   //采用的是全局变量,记录当前的元素是否已经使用了
    public List<List<Integer>> permute(int[] nums) {
        used = new boolean[nums.length];  // 注意,虽然 used是定义的类的成员变量,但是分配空间可以在子函数里面进行
        if (nums.length == 0||nums == null) return res;   //如果数组为空或者数组里面没有元素 返回res
        LinkedList<Integer> p = new LinkedList<>();  //这里定义为LinkedList的目的是在结尾进行增删操作。
                                                     // 如果是ArrayList的话,只能在结尾添加,不能在结尾删除
        permute_recursion( nums,0,p);   // 初始时候。index的值为0,
        return res;
    }

    /**
     *
     * @param nums
     * @param index
     * @param list
     */
    public void permute_recursion(int[] nums, int index, LinkedList<Integer> list ) {
        if (nums.length == index) {    //当前的索引已经超出了 数组的最大的索引 ,说明list中存储的数据已经是一个完整的组合了
            LinkedList<Integer> list_clone = (LinkedList<Integer>)list.clone();//java参数是引用专递机制,发现list中存储的是一个完整的组合后,
                                                                               //将这个组合  克隆一份  添加到res中,否则后续的程序会修改list                 
            res.add(list_clone);  //将克隆后的组合添加到res中
            return;
        }


        for (int i =0;i <nums.length ;i++){
            if (used[i] == false ){   //这里采用的是一个boolean的数组,来记录当前的索引的元素是否已经使用过
                list.addLast(nums[i]); //将新的元素添加到list的尾部,
                used[i]  = true;  // 同时将used的数组 设置为true  表示这个元素已经使用过了
                permute_recursion(nums,index+1,list);  // 递归
                used[i] = false; //清除这个used的标记
                list.removeLast(); // 递归完毕后,将最后一个数据删除,以便于尝试其他的组合,这一步也就是回溯算法的“回溯”的体现所在
            }
        }
    }

47. Permutations II

Given a collection of numbers that might contain duplicates, return all possible unique permutations.

Example:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Input: [1,1,2]
Output:
[
  [1,1,2],
  [1,2,1],
  [2,1,1]
]

解法一:

思路:有重复数字的排列的结果与没有重复数字的排列结果相比较,就是把没有重复数字的排列结果中相同的去掉了。所以我们可以很容易的想到借用java的Set数据结构,可以很容易的排重。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 public List<List<Integer>> permuteUnique(int[] nums) {
        List<List<Integer>> permuteRes = new ArrayList<>();
        Set<LinkedList<Integer>> set = new HashSet<>();
        boolean[] used = new boolean[nums.length];;
        if (nums == null || nums.length == 0) return permuteRes;
        LinkedList<Integer> p = new LinkedList<>();
        permuteHelper(nums,p,set,used);
        return new ArrayList<>(set);
    }

    public void permuteHelper(int[] nums,LinkedList<Integer> list, Set<LinkedList<Integer>> set, boolean[] used){
        if (nums.length == list.size()) {
            LinkedList<Integer> listClone = (LinkedList<Integer>)list.clone();
            set.add(listClone);
            return;
        }
        for (int i = 0 ;i< nums.length;i++){
            if(used[i] == false){
                list.addLast(nums[i]);
                used[i] = true;
                permuteHelper(nums,list,set,used);
                used[i] = false;
                list.removeLast();
            }
        }
    }

解法二:

思路:能不能在查找的过程中就排除重复的情况呢?若果能,那么怎么排除呢?重复的情况又是怎样的呢?带着这些问题,我们发现重复的情况是:拿题目中[1,1,2]来说,如果第一次选1(0),第二次选择1(1),与第一次选1(1),第二次选择1(0),这是同样的结果,这就是重复的原因,所以当索引为i的数字等于索引为i-1的数字时候,如果索引为i-1的数字没有被使用,那么这种情况就是重复的,应该跳过,转化为程序就是:nums[i]== nums[i-1]&&used[i-1]==false,同时要要考虑此时i>0;所以这个时候,for循环中的判断就是if(used[i]||(i>0&&nums[i]== nums[i-1]&&used[i-1]==false)),满足条件则跳过。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    public List<List<Integer>> permuteUnique(int[] nums) {
        Arrays.sort(nums);
        List<List<Integer>> permuteRes = new ArrayList<>();
        boolean[] used = new boolean[nums.length];
        if (nums == null || nums.length == 0) return permuteRes;
        permuteHelper(nums,new LinkedList<>(),permuteRes,used);
        return permuteRes;
    }

    public void permuteHelper(int[] nums,LinkedList<Integer> list, List<List<Integer>> permuteRes, boolean[] used){
        if (nums.length == list.size()) {
            LinkedList<Integer> listClone = (LinkedList<Integer>)list.clone();
            permuteRes.add(listClone);
            return;
        }

        for (int i = 0 ;i< nums.length;i++){
            if(used[i]||(i>0&&nums[i] == nums[i-1]&&used[i-1]==false))  continue;
            list.addLast(nums[i]);
            used[i] = true;
            permuteHelper(nums,list,permuteRes,used);
            used[i] = false;
            list.removeLast();
        }
    }

还要注意的是,这样的解法必须数组数有序的数组,由于LeetCode46中的数组是没有重复数字的,因此不需要有序,所以首先要将数组进行一个排序处理。另外:解法二在查找的过程中就已经排除了重复的值,解法一是将左右的找到放在一个Set中进行去重,这样的时间效率显然没有解法二高。这种解法与解法一的不同之处在于,本解法是在查找到过程中就将不符合要求的情况排除出去,因此算法的效率有很明显的提升。

总结:

对与排列组合问题,首先想到的是采用回溯算法,回溯算法是算法中的几个较为经典的算法之一,这个算法的核心思想就是回溯的过程,代码中的list.removeLast();

很多人认为回溯和递归是一样的,其实不然。在回溯法中可以看到有递归的身影,但是两者是有区别的。回溯法从问题本身出发,寻找可能实现的所有情况。和穷举法的思想相近,不同在于穷举法是将所有的情况都列举出来以后再一一筛选,而回溯法在列举过程如果发现当前情况根本不可能存在,就停止后续的所有工作,返回上一步进行新的尝试。递归是从问题的结果出发,例如求 n!,要想知道 n!的结果,就需要知道 n*(n-1)! 的结果,而要想知道 (n-1)! 结果,就需要提前知道 (n-1)*(n-2)!。这样不断地向自己提问,不断地调用自己的思想就是递归。回溯和递归唯一的联系就是,回溯法可以用递归思想实现。

本算法就是回溯的体现,有关组合的问题可以参照《leetCode 77&39. Combinations & Combination Sum》;

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Docker 基础知识 - 使用卷(volume)管理应用程序数据
卷(volumes)是 Docker 容器生产和使用持久化数据的首选机制。绑定挂载(bind mounts)依赖于主机的目录结构,卷(volumes)完全由 Docker 管理。卷与绑定挂载相比有几个优势:
用户8803964
2021/07/05
3.9K1
docker数据管理
用户在使用Docker的过程中,往往需要能查看容器内应用产生的数据,或者需要把容器内的数据进行备份,甚至多个容器之间进行数据共享,这必然涉及到容器的数据管理操作。
yuezhimi
2020/09/30
7890
​Docker数据管理
在前面我们详细学习了docker的三大核心概念:镜像、容器和仓库,接下来开始学习如何管理数据。在实际工作中使用docker,往往需要对数据进行持久化,或者需要在多个容器之间进行数据共享,此时必然会使用到容器数据管理的各种操作。
啃饼思录
2020/12/29
1.4K0
​Docker数据管理
容器中的数据管理
本节学习的内容是如何管理容器中的数据以及容器之间的数据,我们将要学习如下两个主要方式:
字母哥博客
2020/09/23
8930
Docker 数据管理
我们已经熟悉了 -v 或者 --volume,官方最近建议( Docker 17.06+ ) 使用 --mount。 官方文档:https://docs.docker.com/engine/admin/volumes/ 类型 bind volume tmpfs source source 或 src destination destination 或 dst 或 target volumes 创建 volume $ docker volume create VOLUME_NAME $ dock
康怀帅
2018/02/28
1.6K0
Docker数据管理
数据卷 ( Data Volumes ) 是一个可供容器使用的特殊目录,它将主机操作系统目录直接映射进容器,类似于 Linux 中的 mount 行为 。
海盗船长
2021/12/07
8680
No zuo no die ,用Docker安装Mysql
回显,GENERATED ROOT PASSWORD: Axegh3kAJyDLaRuBemecis&EShOs
birdskyws
2018/09/12
1.2K0
No zuo no die ,用Docker安装Mysql
Docker学习笔记之Docker的数据管理和存储
数据是应用程序重要的产出,所以很好的管理和存储数据,是对应用程序劳动结果的尊重。特别是在大数据时代,所有的数据都是重要的资产,保护好数据是每个开发者必须掌握的技能。我们知道,在 Docker 里,容器运行的文件系统处于沙盒环境中,与外界其实是隔离的,那么我们又要如何在 Docker 中合理的通过文件与外界进行数据交换呢?在这一小节中,我们就来介绍 Docker 中与文件数据有关的内容。
Jetpropelledsnake21
2019/03/04
1K0
Docker学习笔记之Docker的数据管理和存储
Docker 入门到实战教程(六)Docker数据卷
一. 前言 上一篇介绍到如何构建镜像以及镜像管理,不知道大家学到现在有没有疑问?比如我运行web服务产生的日志,我如何在宿主机上看到?我想安装mysql或者redis等,配置文件如何配置,可以进到容器
小东啊
2020/07/23
1.6K0
Docker 入门到实战教程(六)Docker数据卷
Docker核心技术之数据管理
为解决这些问题,docker加入了数据卷(volumes)机制,能很好解决上面问题,以实现:
Lansonli
2021/10/09
4250
Docker实践(三):数据持久化及共享
 在Linux上运行的Docker有三种不同的方式将数据从 Docker Host挂载到 Docker 容器,并实现数据的读取和存储:volumes、bind mounts、tmpfs。  
loong576
2019/09/10
9320
Docker实践(三):数据持久化及共享
Docker应用程序数据管理与持久化
默认情况下,container内部新创建文件或者修改文件,结果会保存在container的可读写层中,这意味着:当container消失时,与container一体的可读写层也一并消失,数据并没有持久化。并且,当一个container需要其它container中可读写层的数据时,取出操作非常困难。
Power
2025/03/03
1420
Docker如何管理数据
http://os.51cto.com/art/201406/443516.htm 到目前我们介绍了一些Docker的基础概念, 知道了如何使用Docker的p_w_picpath, 也知道了如何在多个container间通过网络通讯. 在这章里我们将介绍如何在docker的container内管理数据以及如何在不同的container间共享数据。 我们将介绍两种主要的在docker中管理数据的方法: Data volumes Data volume container Data volumes 一个 data volume 就是一个在一个或者多个container里的特殊用途的目录。它绕过了 Union File System (译者: 这里不确定, 需要研究)为持久化数据、共享数据提供了下面这一些有用的特性: Data volumes 可以在不同的container之间共享和重用数据 对 Data volume 的修改及时生效(译者:data volumn是一个目录, 多个container都挂载这个目录, 具体的可以通过 docker inspect 看 volumne的信息) 对 data volume 修改内容在升级p_w_picpath的时候不会被包括进去 (译者:在docker的整个设计中p_w_picpath是一个无状态的, 这样对升级重用非常有利。而标记状态的数据, 比如数据库的数据, 生产的log之类的应该放到volume里。volume的持久化和恢复在下面有介绍, 是通过文件的形式的, 而不是通过p_w_picpath) Volumes 的持久化直到没有container使用他们 添加数据卷 你可以在docker run 的时候使用 -v 来添加一个 data volume。这个参数在docker run 的时候可以多次使用来添加多个 data volumes。让我们为我们的web application container挂载一个 volume。 $ sudo docker run -d -P --name web -v /webapp training/webapp python app.py 这里一个新的volume会创建到container里的 /webapp. (译者:如果你通过ssh或者通过 -i 登陆到你的container的一个shell里, 使用 ls /webapp 可以验证挂载成功了) 注意: 你也可以在Dockerfile里添加 VOLUME 字段,这样在创建一个新的p_w_picpath的 container是就会自动的创建新的volume. 安装一个目录作为数据卷 使用 -v 不仅能创建一个新的 volume, 还可以把宿主机一个目录mount到container里。 $ sudo docker run -d -P --name web -v /src/webapp:/opt/webapp training/webapp python app.py 这条命令会把本地目录 /src/webapp mount到container里的 /opt/webapp 目录上。用这个方法来测试程序非常 方便, 比如我们可以把我们的源代码通过这个方法mount到container里, 修改本地代码后立即就可以看到修改后的代码是如何在container里工作的了。宿主机的目录必须是绝对路径, 如果这个目录不存在docker会为你自动创建。 注意 这里是没法用 Dockerfile实现的, 因为这样的用法有悖于可移植性和共享. 因为本地目录就像他名字告诉我们的, 是和本地相关的, 不一定可以在所有的宿主机上工作.(译者: 鬼知道你在使用p_w_picpath的时候的host是啥样子的) Docker默认设置volume是可读写的,但是我们也可以mount一个目录为只读: $ sudo docker run -d -P --name web -v /src/webapp:/opt/webapp:ro training/webapp python app.py 这里我们同样mount了 /src/webapp 目录, 但是我们加上了 ro 参数, 告诉docker这个volume是只读的. 创建并安装数据卷容器 如果你有一些持久化的数据, 并且想在不同的container之间共享这些数据, 或者想在一些没有持久化的container中使用, 最好的方法就是使用 Data Volumn Container, 在把数据mount到你的container里.(译者:如开篇译者提到的docker的container是无状态的, 也就是说标记状态的数据,例如:数据库数据, 应用程序的log 等等, 是不应该放到container里的, 而是放到 Data Volume Container里, 这点和f
DevinGeng
2019/04/09
1.1K0
Docker学习——数据管理、使用网络(三)
这一章介绍如何在 Docker 内部以及容器之间管理数据,在容器中管理数据主要有两种方式:
wuweixiang
2018/12/06
5790
06. 管理Docker容器数据
在生产环境中使用 Docker,一方面,需要对数据进行保存或者在多个容器之间进行数据共享;另一方面,在 Docker 的容器被删除后,并不会保留容器的状态信息。那么如何实现信息的持久化呢?这必然涉及容器的数据管理。
有一只柴犬
2024/01/23
1710
06. 管理Docker容器数据
06. 管理Docker容器数据
在生产环境中使用 Docker,一方面,需要对数据进行保存或者在多个容器之间进行数据共享;另一方面,在 Docker 的容器被删除后,并不会保留容器的状态信息。那么如何实现信息的持久化呢?这必然涉及容器的数据管理。
有一只柴犬
2024/01/25
1630
06. 管理Docker容器数据
Docker 学习笔记-数据管理
我们在使用 docker 的时候会将一些数据(例如网站文件、配置文件、数据库文件等)存储在容器中。这样存在一个严重的问题,如果容器出现损坏(例如无法启动,被删除等)那么存储在容器中的数据就会丢失,即使我们进行了容器备份,数据也不可能恢复到故障发生时。如果要解决这个问题,我们就需要用到 docker 的数据管理。在 docker 中数据管理一共有两种方式,分别是数据卷和数据卷容器,下面我们来一一讲解。
喵叔
2020/09/08
5340
Docker 数据管理介绍
默认容器的数据是保存在容器的可读写层,当容器被删除时其上的数据也会丢失,所以为了实现数据的持久性则需要选择一种数据持久技术来保存数据。官方提供了三种存储方式:Volumes、Bind mounts和tmpfs。前面还介绍了:Docker 服务终端 UI 管理工具
民工哥
2021/03/15
7590
Docker 数据管理介绍
Docker存储卷
Docker镜像由多个只读层叠加而成,启动容器时,Docker会加载只读镜像层并在镜像栈顶部添加一个读写层。
Alone-林
2022/08/23
8880
Docker存储卷
02、数据卷(Data Volumes)以及dockefile详解
天蝎座的程序媛
2023/10/17
5650
02、数据卷(Data Volumes)以及dockefile详解
相关推荐
Docker 基础知识 - 使用卷(volume)管理应用程序数据
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验