回溯
2022/11/23 修改,补充C++代码
p a t h path path 保存递归路径, b o o l bool bool 数组记录路径上使用过的数, u u u 指向当前排列的位置,当 u = n u m s . s i z e ( ) u=nums.size() u=nums.size() 刚好组成一个排列,递归弹栈后,恢复现场,即回溯。
C++
class Solution {
public:
vector<vector<int>> ans;
vector<int> path;
vector<vector<int>> permute(vector<int>& nums) {//回溯
bool st[nums.size()];
memset(st,0,sizeof st);
dfs(nums,0,st);
return ans;
}
void dfs(vector<int> &nums,int u,bool st[]){//u当前路径是第几个元素
if(u==nums.size()){
ans.push_back(path);
}
for(int i = 0;i<nums.size();i++){
if(!st[i]){
st[i]=true;
path.push_back(nums[i]);
dfs(nums,u+1,st);
st[i]=false;//回溯
path.pop_back();
}
}
}
};
时间复杂度 O ( n ! × n ) O(n!\times n) O(n!×n) , n u m s nums nums 的长度 n n n ,回溯次数是排列总数的常数倍,排列总数 n ! n! n! ,回溯的时间复杂度 O ( n ! ) O(n!) O(n!) , p u s h _ b a c k push\_back push_back 的时间复杂度 O ( n ) O(n) O(n) ,二者相乘,总时间复杂度 O ( n ! × n ) O(n!\times n) O(n!×n) 。
空间复杂度 O ( n ) O(n) O(n) ,递归压栈的最大深度,和排列长度相等 O ( n ) O(n) O(n) 。
以下是原文。
2022/10/13 首次发布
- 这道题是博主第一次尝试写的复杂递归题。
- 分析题意,找一个数的全排列,按照人的思维逻辑就是确定一个首位,再确定第二位,依次往后,当排列到最后,我们可以回退一个元素,看有没有其他没被选择的排列元素。其实计算机和人的思路很相似,我们的dfs就是上面算法的一个补充。
- 为了确定当前排列是什么,我们建立一个path指针。为了确定哪些元素没有使用过,我们建立一个used指针。全局变量sz,代表所有排列的个数。答案数组ans,用于保存可能的所有排列。
- 看深度优先遍历dfs,传入题目给定的nums,numsSize,我们声明的path,use,ans,再用pos表示当前位置是排列的第几个元素。递归的结束条件,就是pos==numsSize,此时组成一个排列,保存这个排列,回退一个位置。
- 看下一个for循环,考虑递归体,一共递归了numsSize次。让nums数组的每一个元素,都当一次排列的首位。建立排列时,只使用排列里还没出现的元素,所以当false==used[i]时,当前元素才可以放入排列。
- 当我们把一个数放入排列,我们做如下操作
- ①用path[pos]记录这个数,比如pos=3的位置,我们放了nums[3]这个数,依此类推。
- ②我们将used[i]变为true,表明本次递归后,压栈的函数,不能出现当前元素,因为它被使用过。这样就避免了元素重复。
- ③进入递归,递归调用时,pos的位置+1。
- 每一次函数弹栈后,排列往前一步,我们将弹栈前元素的used[i]变为false,之后递归建立排列可以使用该元素。
C
int sz;
void dfs(int *nums,int numsSize,int pos,int *path,bool *used,int **ans){
if(pos == numsSize){//path列举了所有的数
int *res = (int*)calloc(numsSize,sizeof(int));
for(int i =0;i<numsSize;i++){
res[i]=path[i];
}
ans[sz++]=res;
return;
}
for(int i =0;i<numsSize;i++){//每一个数都可以作为首位,组成新的排列
if(false==used[i]){//nums[i]没用过,才递归调用
path[pos] = nums[i];
used[i]=true;//下次递归前,标记当前i已使用
dfs(nums,numsSize,pos+1,path,used,ans);
used[i]=false;//弹栈后,标记当前i未使用
}
}
}
int** permute(int* nums, int numsSize, int* returnSize, int** returnColumnSizes){
bool *used = (bool*)calloc(numsSize,sizeof(bool));//当前数字是否使用过
int *path = (int*)calloc(numsSize,sizeof(int));//排列所经过的路径
int **ans = (int**)calloc(720,sizeof(int*));//最多的排列可能性=6!
sz = 0;
dfs(nums,numsSize,0,path,used,ans);
*returnSize = sz;
*returnColumnSizes=(int*)calloc(sz,sizeof(int));
for(int i = 0;i<sz;i++){
returnColumnSizes[0][i]=numsSize;//每一行对应的元素个数是numsSize
}
return ans;
}
致语
理解思路很重要!
欢迎读者在评论区留言,答主看到就会回复的。
AC