题目:
给定一个不含重复数字的数组 nums
,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
示例一:
输入:nums = [1,2,3] 输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
示例二:
输入:nums = [0,1] 输出:[[0,1],[1,0]]
示例三:
输入:nums = [1] 输出:[[1]]
提示:
1 <= nums.length <= 6
-10 <= nums[i] <= 10
nums
中的所有整数 互不相同思路:
首先还是先来看题目,题目很简单明了,就是找齐全排列的情况,我们可以从提示中得到两点信息。1.nums数组长度最小为1,所以我们不需要对长度进行额外的判断。2.nums数组中的所有整数互不相同,这是很重要的一定。
题目看完了,我们很容易看出用回溯法来做这道题是最合适的,下面是我写的代码。
代码:
class Solution {
public:
vector> res;
vector vec;
set _set;//标记集合
void BackTracking(vector& nums){
if(vec.size() == nums.size()){//退出条件
res.push_back(vec);
return;
}
for(int i = 0; i < nums.size(); i++){//每一层选择
if(_set.find(nums[i]) == _set.end()){//判断是否可以选择
vec.push_back(nums[i]);
_set.insert(nums[i]);
BackTracking(nums);
vec.pop_back();//回退到上一个状态
_set.erase(nums[i]);
}
}
}
vector> permute(vector& nums) {
BackTracking(nums);
return res;
}
};
代码讲解:
vector
> res; vector
vec; set
_set;//标记集合
首先这三个是我们在类中设置的变量,第一个res是一个二维数组,用来保存你最终要返回的答案的。第二个是一个一维数组,是我们用来存储每一个全排列的情况来使用的(这里我们使用vector数组的大容器与小容器的关系,我们直接将小容器(vec)装入大容器(res)中,就完成了二维数组的值的添加)。第三个是一个标记集合,标记集合我们在学习图的时候用到的非常多,这里的含义和在图中的含义是一样的,就是用力啊标记这个树是否已经加入到vec中。
vector
> permute(vector & nums) { BackTracking(nums);
return res;
}
这就是主函数,没什么好说的,先调用了我们写的回溯函数,然后返回res数组。
void BackTracking(vector
& nums){ if(vec.size() == nums.size()){//退出条件
res.push_back(vec);
return;
}
for(int i = 0; i < nums.size(); i++){//每一层选择
if(_set.find(nums[i]) == _set.end()){//判断是否可以选择
vec.push_back(nums[i]);
_set.insert(nums[i]);
BackTracking(nums);
vec.pop_back();//回退到上一个状态
_set.erase(nums[i]);
}
}
}
接下来就是我们回溯的主要代码,首先我们先来讲一下代码,具体的流程我会放在下面。
首先先进来的if判断,用处就是回退的,和递归当中的一样,只不过这时我们完成小容器装入到大容器中这一步。
下面的for循环就是我们在写回溯时要用到的选择系统,我们如何去完成每一次选哪个数字技术通过for循环来完成的。
进入for循环当中,首先先从标记集合中判断当前数字是否已经放入vec中(学过图的同学应该对这个印象会很深),若没有放过,则进入if。
进入if,首先我们讲当前数字放入vec和标记集合当中,然后再次调用回溯函数 ,这里大体上和递归时一模一样的,区别就在于当回退到这里再向下运行函数时,我们完成的状态的回退,就是将vec和标记集合中的当前数字弹出,在弹出之后for循环i++,当前数字改变。
接下来我来给大家描述一下整个流程
这里我们拿示例一【1,2,3】来给大家讲
首先 permute函数调用了我们写的回溯函数,此时推出条件不成立,继续进行下面的代码,进入for循环当中,我们将1放入vec数组和_set标记集合中,然后再次调用回溯函数,(之后的回溯函数我们只说推出条件成立时的情况,不成立我们将不会赘述)进入for循环,此时i=0,nums[i]=1,标记集合当中查到了1已经存在了,所以i++,nums[i]=2,我们将2放入vec和标记集合中,再次调用回溯函数,1和2都在标记集合中存在了,所以3加入到vec和标记集合当中,再次调用回溯函数,此时满足退出条件,【1,2,3】的集合被装入到res中,回到上一层回溯
vec.pop_back();//回退到上一个状态
_set.erase(nums[i]);
我们使用这两条语句完成了状态的回退,将3从vec和标记集合中弹出,for循环结束,回退到上一层回溯,上一层回溯又将2从vec和标记集合中弹出,for循环的i++,此时nums[i]=3,将3加入到vec和标记集合当中,然后再次调用回溯函数,进入for循环发现1在标记集合中,所以将2加入vec和标记集合中,然后再次调用回溯函数,此时满足退出条件,【1,3,2】被装入到res当中,然后回退到上一层回溯,将2从vec和标记集合中弹出,for循环i++,nums[i] = 3,发现3已经在标记集合中了,for循环结束,回退到上一层循环,将3从vec和标记集合中弹出,for循环结束,将1从vec和标记集合中弹出,for循环i++,nums[i]=2,接下来就是和之前流程大差不差了,之不过是以2卡头了。