leetcode
leetcode 1 ~ 50
全排列

全排列

难度:

标签:

题目描述

给定一个不含重复数字的数组 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 <= nums.length <= 6
  • -10 <= nums[i] <= 10
  • nums 中的所有整数 互不相同

代码结果

运行时间: 32 ms, 内存: 15.1 MB


/*
题目思路:
1. 使用Java Stream API来实现全排列生成。
2. 利用Stream的flatMap和collect方法,通过递归的方式构建所有可能的排列。
3. 在递归过程中,每次选择一个数字作为当前排列的下一个元素,并将其从剩余的数字中移除。
*/
 
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
 
public class PermutationsStream {
    public List<List<Integer>> permute(int[] nums) {
        return permute(Arrays.stream(nums).boxed().collect(Collectors.toList())).collect(Collectors.toList());
    }
 
    private Stream<List<Integer>> permute(List<Integer> nums) {
        if (nums.size() == 1) {
            return Stream.of(nums);
        }
        return nums.stream().flatMap(n -> permute(nums.stream()
                .filter(x -> x != n)
                .collect(Collectors.toList()))
                .map(t -> Stream.concat(Stream.of(n), t.stream())
                        .collect(Collectors.toList())));
    }
}
 

解释

方法:

这个题解使用了回溯算法。首先定义了一个 backtrack 函数,它接收一个 path 参数表示当前的排列路径。如果 path 的长度等于 nums 的长度,说明找到了一个完整的排列,将其加入结果列表中并返回。否则,遍历 nums 中的每个元素,如果该元素还没有被使用过(即不为 None),就将其加入到 path 中,并将该元素在 nums 中标记为 None 表示已使用。然后递归调用 backtrack 函数,继续搜索下一个位置的元素。当递归返回时,将之前标记为 None 的元素恢复为原来的值,并从 path 中移除,以便尝试其他的排列方式。最后,在主函数中调用 backtrack 函数,并传入一个空的 path,开始搜索所有可能的排列。

时间复杂度:

O(n * n!)

空间复杂度:

O(n * n!)

代码细节讲解

🦆
为什么在回溯算法中,需要将已选元素标记为 None,而不是直接从 nums 数组中删除它?
在回溯算法中,将已选元素标记为 None 而不是从 nums 数组中删除,是为了避免在删除和插入操作中产生的额外时间开销。使用标记的方法可以直接通过修改数组元素的值来表示这个元素已被使用,这样可以保持数组的结构不变,使得回溯到上一个状态时,只需简单地将标记撤销即可。这种方法比删除元素后再将其插回原位置要高效得多,因为数组的插入和删除操作的时间复杂度为 O(n),而直接修改元素的时间复杂度为 O(1)。
🦆
在回溯过程中,每次递归调用 backtrack 之后,为什么要恢复 nums[i] 的原始值并从 path 中移除最后一个元素?
每次递归调用 backtrack 之后,需要恢复 nums[i] 的原始值并从 path 中移除最后一个元素,是因为这样可以撤销当前递归步骤中的选择,从而保证回溯到上一个状态时,环境与当时的选择前一致。这一操作是回溯算法的核心,即“试错”。如果不将元素恢复原状,那么在返回上一层递归时,上一层的循环将无法正确地继续尝试其他可能的元素,因此这种“状态重置”是实现正确的搜索和回溯的关键。
🦆
如果 nums 数组中包含重复元素,现有的算法还能正确工作吗?如果不能,需要做哪些调整?
如果 nums 数组中包含重复元素,现有的算法可能无法正确处理,因为简单的标记 None 可能导致无法区分不同位置但值相同的元素。为了处理包含重复元素的情况,可以先将 nums 排序,然后修改回溯算法,使用一个额外的布尔数组来跟踪元素的使用状态,而非直接修改 nums 元素为 None。此外,在递归之前检查当前元素是否与前一个元素相同,并且前一个元素的使用状态为未使用,如果是,则跳过当前元素。这样可以防止生成重复的排列。
🦆
在回溯算法中,使用 list(path) 而非直接使用 path 添加到结果集中的原因是什么?
在回溯算法中使用 list(path) 而非直接使用 path 添加到结果集中,是因为 path 在整个回溯过程中是一个被不断修改的变量。如果直接将 path 添加到结果集,那么存储在结果集中的将是 path 的引用,而非其当时的值。随着回溯过程中 path 的修改,结果集中的内容也会跟随变化。使用 list(path) 是为了创建 path 的一个浅拷贝,这样即使后续 path 发生变化,已经添加到结果集中的排列也不会改变,确保存储了每一个独立的排列快照。

相关问题

下一个排列

整数数组的一个 排列  就是将其所有成员以序列或线性顺序排列。

  • 例如,arr = [1,2,3] ,以下这些都可以视作 arr 的排列:[1,2,3][1,3,2][3,1,2][2,3,1]

整数数组的 下一个排列 是指其整数的下一个字典序更大的排列。更正式地,如果数组的所有排列根据其字典顺序从小到大排列在一个容器中,那么数组的 下一个排列 就是在这个有序容器中排在它后面的那个排列。如果不存在下一个更大的排列,那么这个数组必须重排为字典序最小的排列(即,其元素按升序排列)。

  • 例如,arr = [1,2,3] 的下一个排列是 [1,3,2]
  • 类似地,arr = [2,3,1] 的下一个排列是 [3,1,2]
  • arr = [3,2,1] 的下一个排列是 [1,2,3] ,因为 [3,2,1] 不存在一个字典序更大的排列。

给你一个整数数组 nums ,找出 nums 的下一个排列。

必须 原地 修改,只允许使用额外常数空间。

 

示例 1:

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

示例 2:

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

示例 3:

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

 

提示:

  • 1 <= nums.length <= 100
  • 0 <= nums[i] <= 100

全排列 II

给定一个可包含重复数字的序列 nums按任意顺序 返回所有不重复的全排列。

 

示例 1:

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

示例 2:

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

 

提示:

  • 1 <= nums.length <= 8
  • -10 <= nums[i] <= 10

排列序列

给出集合 [1,2,3,...,n],其所有元素共有 n! 种排列。

按大小顺序列出所有排列情况,并一一标记,当 n = 3 时, 所有排列如下:

  1. "123"
  2. "132"
  3. "213"
  4. "231"
  5. "312"
  6. "321"

给定 n 和 k,返回第 k 个排列。

 

示例 1:

输入:n = 3, k = 3
输出:"213"

示例 2:

输入:n = 4, k = 9
输出:"2314"

示例 3:

输入:n = 3, k = 1
输出:"123"

 

提示:

  • 1 <= n <= 9
  • 1 <= k <= n!

组合

给定两个整数 nk,返回范围 [1, n] 中所有可能的 k 个数的组合。

你可以按 任何顺序 返回答案。

 

示例 1:

输入:n = 4, k = 2
输出:
[
  [2,4],
  [3,4],
  [2,3],
  [1,2],
  [1,3],
  [1,4],
]

示例 2:

输入:n = 1, k = 1
输出:[[1]]

 

提示:

  • 1 <= n <= 20
  • 1 <= k <= n