全排列

题目介绍

力扣46题:https://leetcode-cn.com/problems/permutations/
给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
在这里插入图片描述

分析

很明显,n个不同的数的全排列,应该有n!种情形。
这个问题需要暴力穷举。从前到后依次遍历每一个“位置”,每次填入一个数;而之后的一个位置,能够填入的可能性就会少一个。这样,直接用n重循环,考察每个位置就可以得到结果。

不过针对本题,数组的长度是不固定的,直接用多重循环的方式显然不妥。我们可以考虑使用递归。每个位置选取某个值,然后递归地对后面位置取值;由于本位置应该还可以取别的多个值,所以应该在一次递归完毕后,还可以“回到”递归调用的地方,继续取下一个值。所以,这就是回溯遍历的方法。

回溯实现

我们可以定义一个递归函数 backtrack(int i) ,表示当前要对第i个位置进行填充。递归函数分为两个情况:

  • 如果 i >= n,说明我们已经填完了 n 个位置,找到了一个可行的解,我们将solution放入result数组中,递归结束。
  • 如果 i < n,我们可以选择一个尚未使用的数填入当前位置。然后递归调用,对下一个位置进行判断处理。

为了快速判断某个数“尚未使用”,可以用一个set来保存已经填充过的数,使用某个数时直接判断一下是否存在就可以了。

代码如下:

// 回溯实现
public List<List<Integer>> permute1(int[] nums){
    // 定义保存结果的List
    ArrayList<List<Integer>> result = new ArrayList<>();

    // 用一个ArrayList保存一组解
    ArrayList<Integer> solution = new ArrayList<>();

    // 从0位置开始填充数
    backtrack1(nums, result, solution, 0);

    return result;
}

// 定义一个辅助集合,保存已经用过的数
HashSet<Integer> filledNums = new HashSet<>();

// 实现一个回溯方法,方便递归调用
public void backtrack1(int[] nums, List<List<Integer>> result, List<Integer> solution, int i){
    int n = nums.length;
    // 首先判断退出递归调用的场景
    if (i >= n){
        result.add(new ArrayList<>(solution));
    } else {
        // 需要对i位置选数填入,遍历数组中所有数,取没有用过的数进行填充
        for (int j = 0; j < n; j++){
            if (!filledNums.contains(nums[j])){
                // 没用过直接填入
                solution.add(nums[j]);
                filledNums.add(nums[j]);

                // 递归调用,处理下一个位置
                backtrack1(nums, result, solution, i + 1);

                // 回溯,回退状态
                solution.remove(i);
                filledNums.remove(nums[j]);
            }
        }
    }
}

复杂度分析

  • 时间复杂度:O(nn!),其中n为nums长度。递归调用的总次数,就是n个数的全排列数,而每一次调用,都需要复制solution到结果中,这需要耗费O(n)的时间。所以总体的时间复杂度为O(n!) O(n) = O(n*n!)。
  • 空间复杂度:O(n),其中n为nums长度。除结果数组外,用到了辅助的HashSet,额外的内存占用为O(n);另外,递归调用的深度为O(n),所以需要O(n)栈空间。总共的空间复杂度就是O(n)。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值