力扣第46/47题:全排列(回溯算法深度好题)

一、前言

        昨天说了,今天开始回溯算法的题目,今天看了两题非常经典的题目,感觉回溯的套路比动态规划更为明显。今天为什么说两题呢,因为其实47就是46的升级版而已,由无重复数字到有重复数字,我感觉一起讲更有助于理解。话不多说,看题

二、题目内容

力扣第46/47题:全排列(回溯算法深度好题)_第1张图片

力扣第46/47题:全排列(回溯算法深度好题)_第2张图片 

三、题目分析

         首先来看第46题,一个无重复数字的数组求可能全排列,再一看示例内容,呵!这不是排列组合嘛!nums长为3,那答案就是A33=6,没错!确实是这样。但是如果让大家写出每一种情况,可能大家会这样写(假设nums=[1,2,3]):123,132,213,231,312,321,为什么是这个顺序呢,其实就跟树一样,第一次有三个选择,在每一次选择后,剩下的选择只有nums.length-1了。

力扣第46/47题:全排列(回溯算法深度好题)_第3张图片

        如图所示,那么这很明显是二叉树的深度遍历了。我们可以这样干,定义一个列表,然后从头开始遍历二叉树,只要列表的长度等于原数组长度,就给它保存下来,然后回到上个节点,再往另外一个子节点遍历,这样遍历一遍之后就可以保存完整的答案了。我们可以用列表类型的列表保存列表。 

public List> permute(int[] nums) {
        List> list=new ArrayList> ();
        backtrack(list,new ArrayList<>(),nums);
        return list;
    }

        首先创建一个,Integer型列表的列表,然后进行回溯算法的方法,最后结束后返回,就可以得到答案了,仔细观察一下,backtrack中第二个参数很明显使我们用来保存每一次数据的,满足条件后将它加到list中。

        现在看看backtrack该怎么写,回溯算法跟递归一样,需要一个终止点。那么如我刚刚所说,一次回溯的截止点就是当暂时列表里的数据等于原数组的长度,这时候需要将templist加到list中去,并且return,也就是返回到上一层节点去。

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

        但是绝大多数的时候,上面的条件是不成立的,因为此时还没有加完,那么对于这部分,应该怎么处理呢,首先,应该把当前数据加入到templist中,之后递归调用backtrack。最后一步,也是回溯不同于递归的最重要原因,就是撤销,撤销就是将原来添加的数删除,为什么呢?因为list是引用传递,当遍历到叶子节点以后要往回走,往回走的时候必须把之前添加的值给移除了,否则会越加越多。

  tempList.remove(tempList.size() - 1);

        所以完整代码是

    public List> permute(int[] nums) {
        List> list=new ArrayList> ();
        backtrack(list,new ArrayList<>(),nums);
        return list;
    }
    public void backtrack(List> list,List templist,int []nums)
    {
        if(templist.size()==nums.length){
            list.add(new ArrayList<>(templist));
            return ;
        }
        for(int i=0;i

        至于如果数组中有重复数字,我们采取的方法是额外采用一个长度为nums.length的boolean数组,我们先将nums数组排序,相同的数肯定相邻,那在回溯时,判断的方法是:如果当前第i为数被使用过 ,就continue,如果当前数等于前一个数,并且前一个数没有被访问,那么也continue掉,那是因为同样的数留一个就够了。

        这样算的话,代码应该为:

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

四、感言

        回溯算法比动态规划好玩多了

        明天再来

你可能感兴趣的:(回溯,算法,leetcode)