当前位置:网站首页>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)。
边栏推荐
- ServiceMesh主要解决的三大痛点
- Chapter 9 Yunji datacanvas was rated as 36 krypton "the hard core technology enterprise most concerned by investors"
- Section 1: (3) logic chip process substrate selection
- 深入解析Kubebuilder
- 一文搞懂常见的网络I/O模型
- STM32封装ESP8266一键配置函数:实现实现AP模式和STA模式切换、服务器与客户端创建
- Monitoring cannot be started after Oracle modifies the computer name
- 接口间调用为什么要用json、fastjson怎么赋值的、fastjson [email protected]映射关系问题
- How to design API interface and realize unified format return?
- Oracle - views and sequences
猜你喜欢
Sublime tips
【愚公系列】2022年7月 Go教学课程 005-变量
Flex layout and usage
批量归一化(标准化)处理
If you‘re running pod install manually, make sure flutter pub get is executed first.
DFS and BFS concepts and practices +acwing 842 arranged numbers (DFS) +acwing 844 Maze walking (BFS)
Basic idea of counting and sorting
In depth analysis of kubebuilder
Operand of null-aware operation ‘!‘ has type ‘SchedulerBinding‘ which excludes null.
5G VoNR+之IMS Data Channel概念
随机推荐
Flask project uses flask socketio exception: typeerror: function() argument 1 must be code, not str
Operand of null-aware operation ‘!‘ has type ‘SchedulerBinding‘ which excludes null.
2.证券投资基金的概述
Meow, come, come: do you really know if, if else
JS variable plus
3.基金的类型
深入解析Kubebuilder
Code source de la fonction [analogique numérique] MATLAB allcycles () (non disponible avant 2021a)
[line segment tree practice] recent requests + area and retrieval - array modifiable + my schedule I / III
3GPP信道模型路损基础知识
《五》表格
九章云极DataCanvas公司蝉联中国机器学习平台市场TOP 3
Vscode automatically adds a semicolon and jumps to the next line
装饰器基础学习02
Salesforce 容器化 ISV 场景下的软件供应链安全落地实践
ServiceMesh主要解决的三大痛点
01机器学习相关规定
【愚公系列】2022年7月 Go教学课程 005-变量
【ArcGIS教程】专题图制作-人口密度分布图——人口密度分析
Techniques d'utilisation de sublime