LeetCode46全排列(数组中不含重复元素)

全排列>>>
在这里插入图片描述

回溯加递归:每次选择都是从之前选择好的数据中排除,再次进行选择,因此需要一个数组标记,已经选择了哪些数据,若之前选过,则剪枝跳过;使用编程的方法得到全排列,就是在这样的一个树形结构中进行编程,具体来说,就是执行一次深度优先遍历,从树的根结点到叶子结点形成的路径就是一个全排列。

在这里插入图片描述



package BDyNamicProgramming;

import java.util.ArrayList;
import java.util.List;

/**
 * @Author Zhou  jian
 * @Date 2020 ${month}  2020/4/25 0025  15:08
 */
public class Problem46 {


    /**
     * https://leetcode-cn.com/problems/permutations/solution/hui-su-suan-fa-python-dai-ma-java-dai-ma-by-liweiw/
     * 使用编程的方法得到全排列,就是在这样的一个树形结构中进行编程
     * 具体来说:就是执行一次深度优先遍历,从树的根节点到叶子节点形成的路径是一个全排列
     *
     *       每一个节点都表示了全排列问题求解的不同阶段,这些阶段通过变量的不同的值体现
     *       这些变量的不同的值,也称为状态
     *       使用深度优先遍历有回头的过程,在回头以后,状态变量需要设置成和先前一样
     *       因此在回到上一层节点的过程中,需要撤销上一次的选择,这个操作也称为状态重置哦
     *       深度优先遍历,可以直接借助系统栈空间,为我怕你们保存所需要的状态变量,在编码中只需要注意遍历到相应的节点是‘
     *       状态变量的值是正确的,具体的做法是:往下走一次的时候,path变量在尾部追加,而往回走的时候,需要撤销上一次左的选择,也就是在尾部操作,因此
     *

            回溯算法会大量应用“剪值”技巧以达到加快速度,有些时候,需要做一些预处理工作(例如排序)才能达到剪值的目的

            预处理工作虽然也耗时,但一般而且能够剪值节约时间更多

     我做题的时候,第 1 步都是先画图,画图是非常重要的,只有画图才能帮助我们想清楚递归结构,想清楚如何剪枝。就拿题目中的示例,想一想人手动操作是怎么做的,一般这样下来,这棵递归树都不难画出。

     即在画图的过程中思考清楚:

     1、分支如何产生;

     2、题目需要的解在哪里?是在叶子结点、还是在非叶子结点、还是在从跟结点到叶子结点的路径?

     3、哪些搜索是会产生不需要的解的?例如:产生重复是什么原因,如果在浅层就知道这个分支不能产生需要的结果,应该提前剪枝,剪枝的条件是什么,代码怎么写?

     作者:liweiwei1419
     链接:https://leetcode-cn.com/problems/permutations/solution/hui-su-suan-fa-python-dai-ma-java-dai-ma-by-liweiw/
     来源:力扣(LeetCode)
     著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。






     */
    /**
     *
     * 1、首先这颗树除了根节点和叶子节点以外,每个节点左的事情其实一样,即在已经选择了一些数据的前提,
     * 我们需要在剩下还没有选择的数中按照顺序依次选择一个数
     *
     * 2、递归的终止条件,数已经选购了,因此我们需要一个变量来表示当前递归到第几层,我们把这个变量叫做depth
     *
     * 3、这些节点实际上表示了搜索(查找)全排列问题的不同阶段,为了区分这些不同阶段,我们就需要一些变量来记录为了
     * 得到一个圈排列,程序进行到哪一步了,在这里我们需要设置两个变量:
     *
     * ··(1)已经选好了哪些树,到叶子节点时候,这些已经选择数就构成了一个圈排列
     *   (2) 一个布尔数组used,初始化的时候都为fasle,表示这些树都还没有被选择,当我们选定一个数的时候,就将这个
     *   数组的相应位置设置为true,这样在考虑下一个位置的时候,就能够以O(1)的时间复杂度判断这个数是否已经被选择过
     *   这是一种以“空间换时间的”思想
     *
     *     我们把这两个变量称为“状态变量”,他们表示在我们求解一个问题的时候所处的阶段
     *
     * 4、在非叶子结点处,产生不同的分支,这一操作的语义是:在还未选择的数中一次选择一个元素作为下一个位置的元素,这显然通过一个循环实现
     *
     * 5、另外,因为是执行深度优先遍历,从较深层次的节点返回到较浅层次的接地那的时候,需要左状态充值,即回到过去
     *
     * @param nums
     * @return
     */

    public List<List<Integer>> permute(int[] nums){

        List<List<Integer>> rs = new ArrayList<>();
        List<Integer> path = new ArrayList<>();
        
        //标记之前递归的过程中 数组的哪些位置已经使用过
        boolean[] used = new boolean[nums.length];

        fun(nums,path,rs,used,0);

        return rs;


    }


    public void fun(int[] nums,List<Integer> path,List<List<Integer>> rs,boolean[] used,int depth){

        if(depth==nums.length){
            rs.add(new ArrayList<>(path));
        }

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

            //当前nums[i]在前面被使用过则进行剪枝
            if(used[i]){
                continue;
            }

            path.add(nums[i]);
            used[i]=true;

            fun(nums,path,rs,used,depth+1);
            //注意这里是状态重置,是从深层次节点回到浅层次节点的过程,代码在形式上和之前是对成发的
            used[i]=false;
            path.remove((Object)nums[i]);

        }







    }


}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章