1. 题目
读题
https://leetcode.cn/problems/permutations-ii/submissions/428949841/
考查点
这道题的考查点主要有以下几个:
- **回溯法**:回溯法是一种搜索所有可能解的方法,它的基本思想是从一个初始状态开始,每次尝试一个可能的选择,然后递归地搜索剩余的选择空间,如果发现当前的选择不能达到目标或者已经搜索完所有的选择,就回退到上一步,撤销当前的选择,继续尝试其他的选择,直到找到所有的解或者没有解为止。¹²
- **去重**:由于题目给定的序列可能包含重复的数字,所以在搜索全排列时,需要避免生成重复的结果。一种常用的方法是先对数组进行排序,然后在遍历数组时判断当前元素是否和前一个元素相同,如果相同且前一个元素没有被使用过,就跳过当前元素。这样可以保证相同的元素只会出现在同一位置一次。²³
- **编程技巧**:在实现回溯法时,需要注意一些编程技巧,比如如何定义和使用辅助变量(例如结果列表、布尔数组、当前序列等),如何递归地调用辅助函数,如何在递归返回后恢复原来的状态(例如移除当前元素、标记未使用等),以及如何优化代码的效率和可读性。²⁴
2. 解法
思路
回溯法是一种搜索所有可能解的方法,它的基本思想是从一个初始状态开始,每次尝试一个可能的选择,然后递归地搜索剩余的选择空间,如果发现当前的选择不能达到目标或者已经搜索完所有的选择,就回退到上一步,撤销当前的选择,继续尝试其他的选择,直到找到所有的解或者没有解为止。
对于这道题,我们可以把全排列看作是一个长度为n的序列,其中每个位置可以放置数组中的一个元素。我们从第一个位置开始,遍历数组中的每个元素,如果该元素没有被使用过,就把它放在第一个位置,并标记为已使用,然后递归地搜索剩余的n-1个位置。当搜索完所有的位置后,就得到了一个全排列,把它加入结果列表。然后回退到上一步,把当前元素从第一个位置移除,并标记为未使用,继续尝试其他的元素。为了避免重复的全排列,我们需要在遍历数组时判断当前元素是否和前一个元素相同,如果相同且前一个元素没有被使用过,就跳过当前元素。这样可以保证相同的元素只会出现在同一位置一次。
步骤来条理一下回溯法的思路:
- 定义一个结果列表,用来存放所有的全排列。
- 定义一个布尔数组,用来记录每个元素是否被使用过。
- 定义一个辅助函数,用来递归地搜索所有的全排列。
- 在辅助函数中,判断当前的序列是否已经达到了数组的长度,如果是,就把它加入结果列表,返回。
- 如果不是,就遍历数组中的每个元素,如果该元素没有被使用过,就把它加入当前的序列,并标记为已使用,然后递归地搜索剩余的位置。
- 在递归返回后,把当前元素从序列中移除,并标记为未使用,继续尝试其他的元素。
- 为了避免重复的全排列,我们需要在遍历数组时判断当前元素是否和前一个元素相同,如果相同且前一个元素没有被使用过,就跳过当前元素。
- 最后返回结果列表。
具体实现
class Solution { List<List<Integer>> list; // 存放结果 boolean[] used; // 记录元素是否被使用过 public List<List<Integer>> permuteUnique(int[] nums) { list = new ArrayList<>(); // 初始化结果列表 used = new boolean[nums.length]; // 初始化布尔数组 Arrays.sort(nums); // 对数组进行排序 backtrack(new ArrayList<>(), nums); // 回溯 return list; // 返回结果 } private void backtrack(List<Integer> current, int[] nums) { if (current.size() == nums.length) { // 如果当前列表长度等于数组长度,说明找到了一个全排列 list.add(new ArrayList<>(current)); // 把当前列表加入结果列表 } else { // 否则,继续搜索 for (int i = 0; i < nums.length; i++) { // 遍历数组中的每个元素 if (used[i] || i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) { // 如果元素已经被使用过,或者和前一个元素相同且前一个元素没有被使用过,跳过 continue; } used[i] = true; // 标记元素被使用过 current.add(nums[i]); // 把元素加入当前列表 backtrack(current, nums); // 回溯 current.remove(current.size() - 1); // 把元素从当前列表移除 used[i] = false; // 标记元素未被使用过 } } } }