1. 题目
读题
https://leetcode.cn/problems/permutations-ii/submissions/428949841/
考查点
这道题的考查点主要有以下几个:
- **回溯法**:回溯法是一种搜索所有可能解的方法,它的基本思想是从一个初始状态开始,每次尝试一个可能的选择,然后递归地搜索剩余的选择空间,如果发现当前的选择不能达到目标或者已经搜索完所有的选择,就回退到上一步,撤销当前的选择,继续尝试其他的选择,直到找到所有的解或者没有解为止。¹²
- **去重**:由于题目给定的序列可能包含重复的数字,所以在搜索全排列时,需要避免生成重复的结果。一种常用的方法是先对数组进行排序,然后在遍历数组时判断当前元素是否和前一个元素相同,如果相同且前一个元素没有被使用过,就跳过当前元素。这样可以保证相同的元素只会出现在同一位置一次。²³
- **编程技巧**:在实现回溯法时,需要注意一些编程技巧,比如如何定义和使用辅助变量(例如结果列表、布尔数组、当前序列等),如何递归地调用辅助函数,如何在递归返回后恢复原来的状态(例如移除当前元素、标记未使用等),以及如何优化代码的效率和可读性。²⁴
2. 解法
思路
回溯法是一种搜索所有可能解的方法,它的基本思想是从一个初始状态开始,每次尝试一个可能的选择,然后递归地搜索剩余的选择空间,如果发现当前的选择不能达到目标或者已经搜索完所有的选择,就回退到上一步,撤销当前的选择,继续尝试其他的选择,直到找到所有的解或者没有解为止。
对于这道题,我们可以把全排列看作是一个长度为n的序列,其中每个位置可以放置数组中的一个元素。我们从第一个位置开始,遍历数组中的每个元素,如果该元素没有被使用过,就把它放在第一个位置,并标记为已使用,然后递归地搜索剩余的n-1个位置。当搜索完所有的位置后,就得到了一个全排列,把它加入结果列表。然后回退到上一步,把当前元素从第一个位置移除,并标记为未使用,继续尝试其他的元素。为了避免重复的全排列,我们需要在遍历数组时判断当前元素是否和前一个元素相同,如果相同且前一个元素没有被使用过,就跳过当前元素。这样可以保证相同的元素只会出现在同一位置一次。
步骤来条理一下回溯法的思路:
- 定义一个结果列表,用来存放所有的全排列。
- 定义一个布尔数组,用来记录每个元素是否被使用过。
- 定义一个辅助函数,用来递归地搜索所有的全排列。
- 在辅助函数中,判断当前的序列是否已经达到了数组的长度,如果是,就把它加入结果列表,返回。
- 如果不是,就遍历数组中的每个元素,如果该元素没有被使用过,就把它加入当前的序列,并标记为已使用,然后递归地搜索剩余的位置。
- 在递归返回后,把当前元素从序列中移除,并标记为未使用,继续尝试其他的元素。
- 为了避免重复的全排列,我们需要在遍历数组时判断当前元素是否和前一个元素相同,如果相同且前一个元素没有被使用过,就跳过当前元素。
- 最后返回结果列表。
具体实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | 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 ; // 标记元素未被使用过 } } } } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
2019-04-29 Spring 框架用到的 9 个设计模式汇总!
2019-04-29 设计模式总结
2019-04-29 spring中的设计模式
2019-04-29 深入解析spring中用到的九种设计模式