全排序题目属于深度优先最常用的题目之一,当时遇到的时候对它的递归理解不了。
随着对递归的熟练度的增加,递归部分的理解比初次遇到快了很多。
swap(s[step],s[i]);
dfs(s,step+1);
swap(s[step],s[i]);
记得当时的疑问很蠢:递归的过程中真的能保证s[step]和s[i]不会变化吗,如果变化了swap回来不会乱掉嘛…
现在想想其实很好理解,因为递归每次调用都有这三行代码,因为最后一次递归的时候直接return,而回溯至倒数第二次递归,s[step]和s[i]经过两次swap还是原来的样子,再往前回溯也会经历两次swap后才回去,所以是正确的。
而这次二刷主要的疑问在于
1.深度优先实现全排列的思路(第一次其实就应该解决了 )
2.剪枝避免重复的思路
对于第一点,还是从数字的全排列开始逐渐延伸到所有的全排列问题,这里还是以最基础的啊哈算法的数字全排列来分析。这段代码实现了数字1-n的全排列。
void dfs(int step) {
if(step == n + 1) {
for(int i = 1; i <= n; i ++)
printf("%d%c",array[i],i == n ?'\n':' ');
return;
}
for(int i = 1; i <= n; i ++){
if(book[i] == 0){
array[step] = i;
book[i] = 1;
dfs(step + 1);
book[i] = 0;
}
}
return;
}
思路:对于每个step,遍历卡片直到可以得到一张能放置的卡,固定该数字后放入之后进入下一个盒子再进行同样的操作,回溯的时候再收回卡牌。
-----相比于每次都遍历全部元素,我们使用交换元素的办法来完成全排列会省去很多多余步骤
所以修改for循环部分
for(int i=step;i<s.size() ;i++)
{
swap(s[step],s[i]);
dfs(s,step+1);
swap(s[step],s[i]);
}
思路:
对于每个step,我们使s[step]元素与从之后的所有元素交换,交换之后固定进入下一个step,直到回溯的时候换回来。
递归过程
这里来直接手动解析递归的过程。
设最后一个step为n
- 对于每个dfs(step),我们建立一个for循环, 第一个for会循环s.size()次,step不断+1,for循环次数往后依次递减一次直到step==s.size()得到答案回溯。
for(int i=step;i<s.size() ;i++)
- 每个dfs(step)开启第一遍循环后等待后序dfs(step-1)完成回溯。
- 于是一直递归到最后一个元素与自身交换,这时候直接完成dfs(n)。
- 于是回溯dfs(n-1),dfs(n-1)换回元素之后进入for循环第二次,交换自己与下一个位置的元素,再次进入dfs(n)。
- 同样的最后一个元素与自身交换,完成dfs(n)。然后回溯到dfs(n-1)。
- dfs(n-1)的for循环结束,回溯至dfs(n-2)。
- 同理dfs(n-2)拥有两个循环,交换元素后进入dfs(n-1)。排列结束之后再回溯
- 不断经过上述步骤直到全排列。
时间空间复杂度
通过以上描述,我们能看出这种做法的思路,其实有点类似于动态规划,对于第step拆分成s.size()-step个第step-1步来得到其全排列。不断扩展又不断回溯。
且通过递归的思路分析可以得知时间复杂度为nx(n-1)x (n-2)…x 2 x 1 =O(n!)
空间复杂度为n+(n-1)+(n-2)+…+2+1=(n+1)*n/2 = O(n^2)
剪枝
但这还不够完美,因为全排列出现相同的元素的时候,可能出现两次的结果是相同的,
eg s=“abb” 结果有 abb abb bab bba bab bba,
那么这个时候为了得到 abb abb bab bba bab bba ,
我们就需要剪枝了,那么该怎么剪呢,根据我们上述递归的思路,出现重复可能的原因是在某个step步骤中,s[step]与s[i]进行交换,s[i]可能跟step~i之间的某个元素一样,这样交换就没有意义,因为我们for循环的i是从step开始往后的,所以在前面的交换过程中已经将这种情况包括了。
所以可以写出剪枝的函数:
//寻找s[i]到s[j-1]之间有没有元素和s[j]相同,有则返回true
bool cut(vector<int> &s , int i, int j) {
for(int k = i; k < j; k ++)
if(s[k] == s[j]) return true;
return false;
}
所以我们能得到全排列代码
class Solution {
public:
vector<string> ans;
vector<string> permutation(string s) {
dfs(s,0);
return ans;
}
bool cut(string&s,int be,int en)
{
for(int i=be;i<en;i++)
if(s[i]==s[en])
return true;
return false;
}
void dfs(string& s,int step)
{
if(step>=s.size())
{
ans.push_back(s);
return;
}
for(int i=step;i<s.size();i++)
{
if(cut(s,step,i))//剪枝
continue;
swap(s[step],s[i]);
dfs(s,step+1);
swap(s[step],s[i]);
}
}
};
结论
所以基本不可能临时想出来啊!直接记住!