回溯算法
通过DFS方法,“选择操作-递归-撤销操作”以完成回溯算法,目的是为了找到所有可行解。
Leetcode:数组排列
题目:给定一个不含重复数字的整数数组 nums
,返回其 所有可能的全排列 。可以 按任意顺序 返回答案。
解法
1.思路
刚开始我的想法是,通过vector的函数,在每次填数组排列的某一位置时,通过insert等函数加入解数组,并通过erase将待选择数组中被插入解数组的元素抹除,递归后再将抹除的元素重新放回原来的位置。后来发现并不可行,因为在最后一层递归中,待选择数组在抹除后将变为空,这时递归回来就会把最后一个选择的元素放回到待选择数组的第一个位置。
但是通过标记已用和未用的方法更占空间,于是参考了题解。
题解的方式是:利用交换索引分开目前选择的位置和可以选择的元素,即 swap_i为目前在填入答案的位置,而swap_i之后的元素都是可以选择的,包括swap_i本身;在递归时,每次都将交换索引向后移位,并且通过for循环的方式,挑选待选择数组中可以插入相应位置的元素,当swap_i移动到最后时,可选择的只是swap_i索引的这个元素,再次递归使swap_i超出索引范围,这表示一个可行的排列已经找到,将该排列填入最终答案;回溯时,需要把选择的元素放回原来位置,利用for循环选择下一个;此外,学到了要节省空间,能在输入直接操作的交换,不要创造一个数组空间选元素填入。
2.代码及实现过程
代码如下:
class Solution {
public:
// 分别是 全部排列 一个排列 交换索引 数组长度
void backtrack(vector<vector<int>> &ans, vector<int> &ans0, int swap_i,int len)
{
if(swap_i == len) //如果 交换索引到了最后一位 表明没有交换的余地
{
ans.push_back(ans0);
return;
}
for(int i=swap_i;i<len;i++)
{
int temp = ans0[i];
ans0[i] = ans0[swap_i];
ans0[swap_i] = temp; //将交换索引(目前的安排的位置) 和 交换索引后的数字交换
backtrack(ans,ans0,swap_i + 1,len);
temp = ans0[i];
ans0[i] = ans0[swap_i];
ans0[swap_i] = temp; //撤销操作
}
}
vector<vector<int>> permute(vector<int>& nums) {
vector<vector<int> > ans;
backtrack(ans, nums, 0, (int)nums.size());
return ans;
}
};
题解的方法很好,但是我没有想到,主要是:
1.没想到可以分开待选择和已选择的方式。
2.没有想到固定待填入位置,移动指针选择未选择数组。
3.没有直接操作输入的思想。
总结
回溯的基本思想很简单,但是如何实现需要好好想想,base case和递归都需要明确。