Files
leetcode-master/problems/0047.全排列II.md
2023-06-20 08:15:43 +08:00

529 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<p align="center">
<a href="https://programmercarl.com/other/xunlianying.html" target="_blank">
<img src="../pics/训练营.png" width="1000"/>
</a>
<p align="center"><strong><a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p>
# 47.全排列 II
[力扣题目链接](https://leetcode.cn/problems/permutations-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
# 算法公开课
**《代码随想录》算法视频公开课:[回溯算法求解全排列,如何去重?| LeetCode47.全排列 II](https://www.bilibili.com/video/BV1R84y1i7Tm/),相信结合视频再看本篇题解,更有助于大家对本题的理解**。
## 思路
这道题目和[46.全排列](https://programmercarl.com/0046.全排列.html)的区别在与**给定一个可包含重复数字的序列**,要返回**所有不重复的全排列**。
这里又涉及到去重了。
在[40.组合总和II](https://programmercarl.com/0040.组合总和II.html) 、[90.子集II](https://programmercarl.com/0090.子集II.html)我们分别详细讲解了组合问题和子集问题如何去重。
那么排列问题其实也是一样的套路。
**还要强调的是去重一定要对元素进行排序,这样我们才方便通过相邻的节点来判断是否重复使用了**
我以示例中的 [1,1,2]为例 (为了方便举例,已经排序)抽象为一棵树,去重过程如图:
![47.全排列II1](https://code-thinking-1253855093.file.myqcloud.com/pics/20201124201331223.png)
图中我们对同一树层前一位也就是nums[i-1])如果使用过,那么就进行去重。
**一般来说:组合问题和排列问题是在树形结构的叶子节点上收集结果,而子集问题就是取树上所有节点的结果**
在[46.全排列](https://programmercarl.com/0046.全排列.html)中已经详细讲解了排列问题的写法,在[40.组合总和II](https://programmercarl.com/0040.组合总和II.html) 、[90.子集II](https://programmercarl.com/0090.子集II.html)中详细讲解了去重的写法,所以这次我就不用回溯三部曲分析了,直接给出代码,如下:
## C++代码
```CPP
class Solution {
private:
vector<vector<int>> result;
vector<int> path;
void backtracking (vector<int>& nums, vector<bool>& used) {
// 此时说明找到了一组
if (path.size() == nums.size()) {
result.push_back(path);
return;
}
for (int i = 0; i < nums.size(); i++) {
// used[i - 1] == true说明同一树枝nums[i - 1]使用过
// used[i - 1] == false说明同一树层nums[i - 1]使用过
// 如果同一树层nums[i - 1]使用过则直接跳过
if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {
continue;
}
if (used[i] == false) {
used[i] = true;
path.push_back(nums[i]);
backtracking(nums, used);
path.pop_back();
used[i] = false;
}
}
}
public:
vector<vector<int>> permuteUnique(vector<int>& nums) {
result.clear();
path.clear();
sort(nums.begin(), nums.end()); // 排序
vector<bool> used(nums.size(), false);
backtracking(nums, used);
return result;
}
};
// 时间复杂度: 最差情况所有元素都是唯一的。复杂度和全排列1都是 O(n! * n) 对于 n 个元素一共有 n! 中排列方案。而对于每一个答案,我们需要 O(n) 去复制最终放到 result 数组
// 空间复杂度: O(n) 回溯树的深度取决于我们有多少个元素
```
* 时间复杂度: O(n! * n)
* 空间复杂度: O(n)
## 拓展
大家发现,去重最为关键的代码为:
```cpp
if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {
continue;
}
```
**如果改成 `used[i - 1] == true` 也是正确的!**,去重代码如下:
```cpp
if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == true) {
continue;
}
```
这是为什么呢,就是上面我刚说的,如果要对树层中前一位去重,就用`used[i - 1] == false`,如果要对树枝前一位去重用`used[i - 1] == true`
**对于排列问题,树层上去重和树枝上去重,都是可以的,但是树层上去重效率更高!**
这么说是不是有点抽象?
来来来,我就用输入: [1,1,1] 来举一个例子。
树层上去重(used[i - 1] == false),的树形结构如下:
![47.全排列II2](https://code-thinking-1253855093.file.myqcloud.com/pics/20201124201406192.png)
树枝上去重used[i - 1] == true的树型结构如下
![47.全排列II3](https://code-thinking-1253855093.file.myqcloud.com/pics/20201124201431571.png)
大家应该很清晰的看到,树层上对前一位去重非常彻底,效率很高,树枝上对前一位去重虽然最后可以得到答案,但是做了很多无用搜索。
## 总结
这道题其实还是用了我们之前讲过的去重思路,但有意思的是,去重的代码中,这么写:
```cpp
if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {
continue;
}
```
和这么写:
```cpp
if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == true) {
continue;
}
```
都是可以的,这也是很多同学做这道题目困惑的地方,知道`used[i - 1] == false`也行而`used[i - 1] == true`也行,但是就想不明白为啥。
所以我通过举[1,1,1]的例子,把这两个去重的逻辑分别抽象成树形结构,大家可以一目了然:为什么两种写法都可以以及哪一种效率更高!
这里可能大家又有疑惑,既然 `used[i - 1] == false`也行而`used[i - 1] == true`也行,那为什么还要写这个条件呢?
直接这样写 不就完事了?
```cpp
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
```
其实并不行,一定要加上 `used[i - 1] == false`或者`used[i - 1] == true`,因为 used[i - 1] 要一直是 true 或者一直是false 才可以,而不是 一会是true 一会又是false 所以这个条件要写上。
是不是豁然开朗了!!
## 其他语言版本
### java
```java
class Solution {
//存放结果
List<List<Integer>> result = new ArrayList<>();
//暂存结果
List<Integer> path = new ArrayList<>();
public List<List<Integer>> permuteUnique(int[] nums) {
boolean[] used = new boolean[nums.length];
Arrays.fill(used, false);
Arrays.sort(nums);
backTrack(nums, used);
return result;
}
private void backTrack(int[] nums, boolean[] used) {
if (path.size() == nums.length) {
result.add(new ArrayList<>(path));
return;
}
for (int i = 0; i < nums.length; i++) {
// used[i - 1] == true说明同⼀树⽀nums[i - 1]使⽤过
// used[i - 1] == false说明同⼀树层nums[i - 1]使⽤过
// 如果同⼀树层nums[i - 1]使⽤过则直接跳过
if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {
continue;
}
//如果同⼀树⽀nums[i]没使⽤过开始处理
if (used[i] == false) {
used[i] = true;//标记同⼀树⽀nums[i]使⽤过,防止同一树枝重复使用
path.add(nums[i]);
backTrack(nums, used);
path.remove(path.size() - 1);//回溯说明同⼀树层nums[i]使⽤过,防止下一树层重复
used[i] = false;//回溯
}
}
}
}
```
### python
```python
class Solution:
def permuteUnique(self, nums):
nums.sort() # 排序
result = []
self.backtracking(nums, [], [False] * len(nums), result)
return result
def backtracking(self, nums, path, used, result):
if len(path) == len(nums):
result.append(path[:])
return
for i in range(len(nums)):
if (i > 0 and nums[i] == nums[i - 1] and not used[i - 1]) or used[i]:
continue
used[i] = True
path.append(nums[i])
self.backtracking(nums, path, used, result)
path.pop()
used[i] = False
```
### Go
```go
var (
res [][]int
path []int
st []bool // state的缩写
)
func permuteUnique(nums []int) [][]int {
res, path = make([][]int, 0), make([]int, 0, len(nums))
st = make([]bool, len(nums))
sort.Ints(nums)
dfs(nums, 0)
return res
}
func dfs(nums []int, cur int) {
if cur == len(nums) {
tmp := make([]int, len(path))
copy(tmp, path)
res = append(res, tmp)
}
for i := 0; i < len(nums); i++ {
if i != 0 && nums[i] == nums[i-1] && !st[i-1] { // 去重用st来判别是深度还是广度
continue
}
if !st[i] {
path = append(path, nums[i])
st[i] = true
dfs(nums, cur + 1)
st[i] = false
path = path[:len(path)-1]
}
}
}
```
### Javascript
```javascript
var permuteUnique = function (nums) {
nums.sort((a, b) => {
return a - b
})
let result = []
let path = []
function backtracing( used) {
if (path.length === nums.length) {
result.push([...path])
return
}
for (let i = 0; i < nums.length; i++) {
if (i > 0 && nums[i] === nums[i - 1] && !used[i - 1]) {
continue
}
if (!used[i]) {
used[i] = true
path.push(nums[i])
backtracing(used)
path.pop()
used[i] = false
}
}
}
backtracing([])
return result
};
```
### TypeScript
```typescript
function permuteUnique(nums: number[]): number[][] {
nums.sort((a, b) => a - b);
const resArr: number[][] = [];
const usedArr: boolean[] = new Array(nums.length).fill(false);
backTracking(nums, []);
return resArr;
function backTracking(nums: number[], route: number[]): void {
if (route.length === nums.length) {
resArr.push([...route]);
return;
}
for (let i = 0, length = nums.length; i < length; i++) {
if (i > 0 && nums[i] === nums[i - 1] && usedArr[i - 1] === false) continue;
if (usedArr[i] === false) {
route.push(nums[i]);
usedArr[i] = true;
backTracking(nums, route);
usedArr[i] = false;
route.pop();
}
}
}
};
```
### Swift
```swift
func permuteUnique(_ nums: [Int]) -> [[Int]] {
let nums = nums.sorted() // 先排序,以方便相邻元素去重
var result = [[Int]]()
var path = [Int]()
var used = [Bool](repeating: false, count: nums.count)
func backtracking() {
if path.count == nums.count {
result.append(path)
return
}
for i in 0 ..< nums.count {
// !used[i - 1]表示同一树层nums[i - 1]使用过,直接跳过,这一步很关键!
if i > 0, nums[i] == nums[i - 1], !used[i - 1] { continue }
if used[i] { continue }
used[i] = true
path.append(nums[i])
backtracking()
// 回溯
path.removeLast()
used[i] = false
}
}
backtracking()
return result
}
```
### Rust
```Rust
impl Solution {
fn backtracking(result: &mut Vec<Vec<i32>>, path: &mut Vec<i32>, nums: &Vec<i32>, used: &mut Vec<bool>) {
let len = nums.len();
if path.len() == len {
result.push(path.clone());
return;
}
for i in 0..len {
if i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false { continue; }
if used[i] == false {
used[i] = true;
path.push(nums[i]);
Self::backtracking(result, path, nums, used);
path.pop();
used[i] = false;
}
}
}
pub fn permute_unique(nums: Vec<i32>) -> Vec<Vec<i32>> {
let mut result: Vec<Vec<i32>> = Vec::new();
let mut path: Vec<i32> = Vec::new();
let mut used = vec![false; nums.len()];
let mut nums= nums;
nums.sort();
Self::backtracking(&mut result, &mut path, &nums, &mut used);
result
}
}
```
### C
```c
//临时数组
int *path;
//返回数组
int **ans;
int *used;
int pathTop, ansTop;
//拷贝path到ans中
void copyPath() {
int *tempPath = (int*)malloc(sizeof(int) * pathTop);
int i;
for(i = 0; i < pathTop; ++i) {
tempPath[i] = path[i];
}
ans[ansTop++] = tempPath;
}
void backTracking(int* used, int *nums, int numsSize) {
//若path中元素个数等于numsSize将path拷贝入ans数组中
if(pathTop == numsSize)
copyPath();
int i;
for(i = 0; i < numsSize; i++) {
//若当前元素已被使用
//或前一位元素与当前元素值相同但并未被使用
//则跳过此分支
if(used[i] || (i != 0 && nums[i] == nums[i-1] && used[i-1] == 0))
continue;
//将当前元素的使用情况设为True
used[i] = 1;
path[pathTop++] = nums[i];
backTracking(used, nums, numsSize);
used[i] = 0;
--pathTop;
}
}
int cmp(void* elem1, void* elem2) {
return *((int*)elem1) - *((int*)elem2);
}
int** permuteUnique(int* nums, int numsSize, int* returnSize, int** returnColumnSizes){
//排序数组
qsort(nums, numsSize, sizeof(int), cmp);
//初始化辅助变量
pathTop = ansTop = 0;
path = (int*)malloc(sizeof(int) * numsSize);
ans = (int**)malloc(sizeof(int*) * 1000);
//初始化used辅助数组
used = (int*)malloc(sizeof(int) * numsSize);
int i;
for(i = 0; i < numsSize; i++) {
used[i] = 0;
}
backTracking(used, nums, numsSize);
//设置返回的数组的长度
*returnSize = ansTop;
*returnColumnSizes = (int*)malloc(sizeof(int) * ansTop);
int z;
for(z = 0; z < ansTop; z++) {
(*returnColumnSizes)[z] = numsSize;
}
return ans;
}
```
### Scala
```scala
object Solution {
import scala.collection.mutable
def permuteUnique(nums: Array[Int]): List[List[Int]] = {
var result = mutable.ListBuffer[List[Int]]()
var path = mutable.ListBuffer[Int]()
var num = nums.sorted // 首先对数据进行排序
def backtracking(used: Array[Boolean]): Unit = {
if (path.size == num.size) {
// 如果path的size等于num了那么可以添加到结果集
result.append(path.toList)
return
}
// 循环守卫,当前元素没被使用过就进入循环体
for (i <- num.indices if used(i) == false) {
// 当前索引为0不存在和前一个数字相等可以进入回溯
// 当前索引值和上一个索引不相等,可以回溯
// 前一个索引对应的值没有被选,可以回溯
// 因为Scala没有continue只能将逻辑反过来写
if (i == 0 || (i > 0 && num(i) != num(i - 1)) || used(i-1) == false) {
used(i) = true
path.append(num(i))
backtracking(used)
path.remove(path.size - 1)
used(i) = false
}
}
}
backtracking(new Array[Boolean](nums.length))
result.toList
}
}
```
<p align="center">
<a href="https://programmercarl.com/other/kstar.html" target="_blank">
<img src="../pics/网站星球宣传海报.jpg" width="1000"/>
</a>