全排列12

46. 全排列

给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。

示例 1:

输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

示例 2:

输入:nums = [0,1]
输出:[[0,1],[1,0]]

示例 3:

输入:nums = [1]
输出:[[1]]

如何遍历所有排列?

这里选择一个比较通俗易懂的说法。也就是字典序法

假设你手上有三张牌,1、2、3。三个放牌的位置,我们需要从左往右寻找所有的排列组合。

如果我们按照每一个位置按1,2,3的顺序来排放,

第一个位置 1 第二个位置 2  第三个位置 3 ,然后将牌回收直到可以排下一个组合

第一个位置 1 第二个位置 3  第三个位置 2 ,在回收了牌之后,不能放原来的牌

第一个位置 2 第二个位置 1  第三个位置 3

第一个位置 2 第二个位置 3  第三个位置 1 。。。。。

这样我们可以得到所有序列,我们来模拟这个过程,发现比较困难。

我们缩小问题规模,数组[1,2],位置[1,2]

第一个位置 1 第二个位置 2 

第一个位置 2 第二个位置 1

再缩小数组[1],位置[1]

第一个位置 1

寻找规律,可以得到,第1个位置上重新排列,其排序结果相当于2~n的子序列的一次重新排列。进而可以理解

全排列公式:n!=n*(n-1)*(n-2)*....1;

这样排列的规律是:每一个位置按从小到大的顺序来排放,前一个位置的排列重置后面子序列的排列。

根据这个规律可以写出流程:

第一个位置 放1,第二个位置 放2 第三个位置 放3,没有下一个位置了。产生一个序列。

收回牌,收回3位置的牌,3,由于每一个位置递增排列,没有比三大的数,回退。

收回第二个位置的牌,2,第二个位置可以放3。排第三个位置。

第三个位置重排,可以现有牌的最小牌,2,没有下一个位置了。产生一个序列。

。。。。。

如何实现这个流程,解决问题:

1、如何放牌?

        1)将手里的牌从小到大放牌,如果没有可选择的牌就结束放牌。如果有,就放完该位置,保存该位置的状态,准备放下一个位置。

  我们来标注一下保存该位置的状态,到时候注意这个操作会产生的效果

将手里的牌从小到大放牌,这个产生的效果可以是这样

  因为我们规定按照每一个位置按1,2,3的顺序来排放----->这个可以由循环实现

 但是我们如何将这个状态转化为下一个状态呢?

 

保存每一个位置的状态,由于我们只放牌,我们只需要知道,我们把哪一张牌放到了哪一个位置,递归能够很好的解决保存状态的问题。

产生的效果:当保存状态之后,将手里的牌从小到大放牌,当回退重排时,就会基于上一个回收的牌,变成从x+1到更大放牌,至此我们实现了第二种排列

我们在通过放牌的思路,梳理一下其他的状态,都可以完成,至此我们放牌的策略算是构建成功。

但是这个实现是基于收牌的基础上的,我们来看一下如何收牌?

 2、如何收牌? 没有下一个位置时或者回收位置没有可排序序列时收牌,表示递归结束。至此我们的问题变成了如何实现递归算法。

递归

 

问题1:如何递推?每一个位置,通过循环从小到大递增寻找没有使用的元素,为每一个位置赋值,并标记为已经使用,记录该位置的信息,并为下一个位置赋值。

//代码实现

//book[]数组表示下标为i的元素的状态,如果没有放为true,放了为false

//每一个存放数据的位置,通过循环从小到大递增寻找没有使用的元素

boolean[] book=new boolean[4]{true};//0不用,我们使用的牌从1开始
//boolean[]类型不能这样初始化
for(int i=1;i<=3;i++){//循环从小到大,同时也记录了当前所放的牌是哪一张。
//这张牌回收之后用的是下一张
        if(book[i]==true){//如果牌这张牌没有放,可以将这张牌放下去,并标记这张牌已经被放了。
            a[x]=i;
            book[i]=false;
            dfs(x+1);
        }
}

问题2:  如何回溯?递归实现给下一个位置赋值,回退,标记当前元素为未被使用的状态,也就是回收当前位置上的值。

boolean[] book=new boolean[4]{true};//0不用,我们使用的牌从1开始
public void dfs(int x)//表示存储点,从0开始
{
    if(x==size+1)//表示存储点已经没有了
        return ;
    for(int i=1;i<=3;i++){//循环从小到大,同时也记录了当前所放的牌是哪一张。
//这张牌回收之后用的是下一张
        if(book[i]==true){//如果牌这张牌没有放,可以将这张牌放下去,并标记这张牌已经被放了。
            a[x]=i;
            book[i]=false;
            dfs(x+1);
            book[i]=true;
        }
        //如果这张牌已经用过了,就看下一张牌有没有用过
    }
}

代码实现

class Solution {
    List<List<Integer>> arr=new ArrayList();
    int[] book;//用于标记下标为i的数据是否已经被使用,0为未被使用
    int len;//数据的个数==存储位置的个数
    List<Integer> list=new ArrayList<>();
    public List<List<Integer>> permute(int[] nums) {
            book=new int[nums.length];
            len=nums.length;
            f(nums,1);//位置从1开始
            return arr; 
    }   
    public void f(int[] nums,int x)//x表示当前位置
    {
        if(x==len+1)//存储位置的下一位
        {
            arr.add(new ArrayList<Integer>(list));
            return ;
        }
        for(int i=0;i<len;i++)
        {
            if(book[i]==0){
                list.add(nums[i]);//往末尾添加元素
                book[i]=1;
                f(nums,x+1);
                list.remove(list.size()-1);//删除末尾元素
                book[i]=0;
            }
        }
    }
}

如果数组中存在重复的数字?

如1 2 2 如何处理?相关题解,请看我大哥的题解.

set实现去重的原理是:在前一个位置未重排前,防止重复的数填入同一个位置。

比如 1 2 2

回收2 2,当前的set中存储了一个2,就不能重新,set记录的是当前执行函数中的该位置的存储记录。生命周期是当前递归函数。

去重三行搞定,放入合适的位置。

Set<Integer> set=new HashSet<>();
if(set.contains(nums[i])) continue;
                set.add(nums[i]);

关键代码

 Set<Integer> set=new HashSet<>();
        for(int i=0;i<len;i++)
        {   
            if(book[i]==0){
                if(set.contains(nums[i])) continue;
                set.add(nums[i]);
                list.add(nums[i]);//往末尾添加元素
                book[i]=1;
                f(nums,x+1);
                list.remove(list.size()-1);//删除末尾元素
                book[i]=0;
            }
        }

代码

class Solution {
     List<List<Integer>> arr=new ArrayList();
    int[] book;//用于标记下标为i的数据是否已经被使用,0为未被使用
    int len;//数据的个数==存储位置的个数
    List<Integer> list=new ArrayList<>();
    public List<List<Integer>> permuteUnique(int[] nums) {
        book=new int[nums.length];
            len=nums.length;
            f(nums,1);//位置从1开始
            return arr; 
    }
    public void f(int[] nums,int x)//x表示当前位置
    {
        if(x==len+1)//存储位置的下一位
        {
            arr.add(new ArrayList<Integer>(list));
            return ;
        }
        Set<Integer> set=new HashSet<>();
        for(int i=0;i<len;i++)
        {   
            if(book[i]==0){
                if(set.contains(nums[i])) continue;
                set.add(nums[i]);
                list.add(nums[i]);//往末尾添加元素
                book[i]=1;
                f(nums,x+1);
                list.remove(list.size()-1);//删除末尾元素
                book[i]=0;
            }
        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值