当前位置:网站首页>Leetcode(46)——全排列
Leetcode(46)——全排列
2022-07-06 22:50:00 【SmileGuy17】
Leetcode(46)——全排列
题目
给定一个不含重复数字的数组 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 1 1 <= nums.length <= 6 6 6
- − 10 -10 −10 <= nums[i] <= 10 10 10
- n u m s nums nums 中的所有整数 互不相同
题解
方法一:回溯法
思路
怎样输出所有的排列方式呢?对于每一个当前位置 i,我们可以将其于之后的任意位置交换,然后继续处理位置 i+1,直到处理到最后一位。为了防止每次遍历时都要新建一个子数组储存位置 i 之前已经交换好的数字——我们可以利用回溯法,只对原数组进行修改,在递归完成后再修改回来。
具体来说,假设我们已经填到第 first \textit{first} first 个位置,那么 nums \textit{nums} nums 数组中 [ 0 , first − 1 ] [0, \textit{first} - 1] [0,first−1] 是已填过的数的集合, [ first , n − 1 ] [\textit{first}, n - 1] [first,n−1] 是待填的数的集合。我们肯定是尝试用 [ first , n − 1 ] [\textit{first}, n - 1] [first,n−1] 里的数去填第 first \textit{first} first 个数,假设待填的数的下标为 i i i,那么填完以后我们将第 i i i 个数和第 first \textit{first} first 个数交换,即能使得在填第 first + 1 \textit{first} + 1 first+1 个数的时候 nums \textit{nums} nums 数组的 [ 0 , first ] [0, \textit{first}] [0,first] 部分为已填过的数, [ first + 1 , n − 1 ] [\textit{first} + 1, n - 1] [first+1,n−1] 为待填的数,回溯的时候交换回来即能完成撤销操作。
举个简单的例子,假设我们有 [ 2 , 5 , 8 , 9 , 10 ] [2, 5, 8, 9, 10] [2,5,8,9,10] 这 5 5 5 个数要填入,已经填到第 3 3 3 个位置,已经填了 [ 8 , 9 ] [8, 9] [8,9] 两个数,那么这个数组目前为 [ 8 , 9 ∣ 2 , 5 , 10 ] [8, 9~|~2, 5, 10] [8,9 ∣ 2,5,10] 这样的状态,分隔符区分了左右两个部分。假设这个位置我们要填 10 10 10 这个数,为了维护数组,我们将 2 2 2 和 10 10 10 交换,即能使得数组继续保持分隔符左边的数已经填过,右边的待填 [ 8 , 9 , 10 ∣ 2 , 5 ] [8, 9, 10~|~2, 5] [8,9,10 ∣ 2,5]。
当然我们会发现这样生成的全排列并不是按字典序存储在答案数组中的,如果题目要求按字典序输出,那么请还是用标记数组或者其他方法。
下面的图展示了回溯的整个过程:
代码实现
Leetcode 官方题解:
class Solution {
public:
void backtrack(vector<vector<int>>& res, vector<int>& output, int first, int len){
// 所有数都填完了
if (first == len) {
res.emplace_back(output);
return;
}
for (int i = first; i < len; ++i) {
// 动态维护数组
swap(output[i], output[first]);
// 继续递归填下一个数
backtrack(res, output, first + 1, len);
// 撤销操作
swap(output[i], output[first]);
}
}
vector<vector<int>> permute(vector<int>& nums) {
vector<vector<int> > res;
backtrack(res, nums, 0, (int)nums.size());
return res;
}
};
我自己的:
class Solution {
void DFS(vector<vector<int>>& ans, vector<int>& s, vector<int>& nums, int p){
if(s.size() == nums.size()) ans.push_back(s);
else{
for(int n = p; n < nums.size(); n++){
swap(nums[p], nums[n]);
s.push_back(nums[p]);
DFS(ans, s, nums, p+1); // 从下标1开始
s.pop_back(); // 回溯
swap(nums[p], nums[n]);
}
}
}
public:
vector<vector<int>> permute(vector<int>& nums) {
vector<int> s;
vector<vector<int>> ans;
DFS(ans, s, nums, 0);
return ans;
}
};
改进后:
class Solution {
void DFS(vector<vector<int>>& ans, vector<int>& nums, int p){
if(p+1 == nums.size())
ans.push_back(nums);
else{
for(int n = p; n < nums.size(); n++){
swap(nums[p], nums[n]);
DFS(ans, nums, p+1); // 从下标 p+1 开始
swap(nums[p], nums[n]); // 回溯
}
}
}
public:
vector<vector<int>> permute(vector<int>& nums) {
vector<vector<int>> ans;
DFS(ans, nums, 0);
return ans;
}
};
复杂度分析
- 时间复杂度: O ( n × n ! ) O(n \times n!) O(n×n!),其中 n n n 为序列的长度。
算法的复杂度首先受 backtrack \textit{backtrack} backtrack 的调用次数制约, backtrack \textit{backtrack} backtrack 的调用次数为 ∑ k = 1 n P ( n , k ) \sum_{k = 1}^{n}{P(n, k)} ∑k=1nP(n,k) 次,其中 P ( n , k ) = n ! ( n − k ) ! = n ( n − 1 ) … ( n − k + 1 ) P(n, k) = \frac{n!}{(n - k)!} = n (n - 1) \ldots (n - k + 1) P(n,k)=(n−k)!n!=n(n−1)…(n−k+1),该式被称作 n 的 k - 排列,或者部分排列。
而 ∑ k = 1 n P ( n , k ) = n ! + n ! 1 ! + n ! 2 ! + n ! 3 ! + … + n ! ( n − 1 ) ! < 2 n ! + n ! 2 + n ! 2 2 + n ! 2 n − 2 < 3 n ! \sum_{k = 1}^{n}{P(n, k)} = n! + \frac{n!}{1!} + \frac{n!}{2!} + \frac{n!}{3!} + \ldots + \frac{n!}{(n-1)!} < 2n! + \frac{n!}{2} + \frac{n!}{2^2} + \frac{n!}{2^{n-2}} < 3n! ∑k=1nP(n,k)=n!+1!n!+2!n!+3!n!+…+(n−1)!n!<2n!+2n!+22n!+2n−2n!<3n!
这说明 backtrack \textit{backtrack} backtrack 的调用次数是 O ( n ! ) O(n!) O(n!) 的。
而对于 backtrack \textit{backtrack} backtrack 调用的每个叶结点(共 n ! n! n! 个),我们需要将当前答案使用 O ( n ) O(n) O(n) 的时间复制到答案数组中,相乘得时间复杂度为 O ( n × n ! ) O(n \times n!) O(n×n!)。
因此时间复杂度为 O ( n × n ! ) O(n \times n!) O(n×n!)。 - 空间复杂度: O ( n ) O(n) O(n),其中 n n n 为序列的长度。除答案数组以外,递归函数在递归过程中需要为每一层递归函数分配栈空间,所以这里需要额外的空间且该空间取决于递归的深度,这里可知递归调用深度为 O ( n ) O(n) O(n)。
边栏推荐
- Run the command once per second in Bash- Run command every second in Bash?
- AttributeError: module ‘torch._C‘ has no attribute ‘_cuda_setDevice‘
- 一个酷酷的“幽灵”控制台工具
- 第一篇论文的写作流程
- JDBC link Oracle reference code
- Batch normalization (Standardization) processing
- JS variable case
- acwing 843. N-queen problem
- Tiktok may launch an independent grass planting community platform: will it become the second little red book
- [digital analog] source code of MATLAB allcycles() function (not available before 2021a)
猜你喜欢
随机推荐
A line of R code draws the population pyramid
Common Oracle SQL statements
App embedded H5 --- iPhone soft keyboard blocks input text
R descriptive statistics and hypothesis testing
Appium practice | make the test faster, more stable and more reliable (I): slice test
[Yugong series] go teaching course 005 variables in July 2022
Introduction to namespace Basics
Programmers go to work fishing, so play high-end!
Ansible overview and module explanation (you just passed today, but yesterday came to your face)
使用Thread类和Runnable接口实现多线程的区别
How to package the parsed Excel data into objects and write this object set into the database?
深入解析Kubebuilder
Two methods of chromosome coordinate sequencing
np.random.shuffle与np.swapaxis或transpose一起时要慎用
3.基金的类型
ClickHouse(03)ClickHouse怎么安装和部署
Basic knowledge of road loss of 3GPP channel model
A simple and beautiful regression table is produced in one line of code~
一个酷酷的“幽灵”控制台工具
STM32F103 realize IAP online upgrade application



![[736. LISP syntax parsing]](/img/62/5e2aeec150096aa3fd81025d146255.png)





