题目陈述
大意:给定一个字符串,求出它的字符的所有排序(元素可能有重复),并且答案按从小到大给出
方法一:做法
算法思路
- 递归搜索进行搜素,对于第h层,枚举第h个位置,应该填入什么下标的元素
- 注意:此处因为字符串可能有重复的元素,故我们记录的是下标,因为下标不可能重复(相当于一种没有哈希冲突的哈希函数)
- 然后我们也需要开一个bool数组来记录哪些下标没有被使用过,确保同一下标只被使用过一次
- 递归边界条件h==s.size(),因为第h层代表正在枚举第h个位置(即前面h-1个位置已经枚举完毕),所以第h=s.size()层代表前面s.size()-1层都已经枚举完毕
- 回缩的时候,记得将原来状态恢复即可
- 时间复杂度,每一层都需要枚举所有数是否能使用,总共需要枚举n层
代码实现
class Solution { public: void dfs(int h,string &s,string cur,set &ans,vector &f){ if(h==s.size()){//递归边界 ans.insert(cur);//找到一个答案并且插入 return ; } for(int i=0;i<s.size();i++){ if(!f[i]){//当前位置的字符未被使用 f[i]=1;//则使用 cur+=s[i];//更新当前字符排序 dfs(h+1,s,cur,ans,f);//递归下一层 cur.pop_back();//还原当前字符排序 f[i]=0;//还原f } } } vector Permutation(string str) { if(str.empty())return {}; set ans;//因为题目说str中可能有重复元素,所以需要集合来去重,并且起到从小到大排序作用 vector f(str.size(),0);//用来标记第i个位置是否已被使用 string cur;//当前的搜索出来的字符串 dfs(0,str,cur,ans,f);//进行递归搜索字符串排序 return vector({ans.begin(),ans.end()});//vector迭代器拷贝初始化,只需要元素的类型一样即可,跟容器无关 } };
思考:是否一定需要用set
- 肯定有同学在这边会有疑问,set内嵌红黑树,每次维护需要花费的代价,但是如果我们一开始先将str排序一下,是不是就不需要set了呢?或者可以将set换成vector?
- 答案是显然的,不可以。为什么?因为题目里面的元素可能是重复的,所以最后总的排序个数可能小于(N!)
- 就算我们先排序,最后求出的答案,在vector中依旧会面临一个去重的问题,依旧需要借助set来执行,所以此处我们set还是不能被替换为vector的
算法二:做法
算法思路
- 我们可以发现,全排列的个数也就刚好个,我们这个算法的时间复杂度也刚好是,可以说,已经是最优的了
- 此处我们先说算法的复杂度,随后再证明正确性
- 对于字符串s,递归搜索,对于第h层,依次和下标在[h,s.size()-1]中的元素互换
- 递归边界为h==s.size()-1,(自己跟自己换此时没有意义)
- 我们先来看递归代码,顺带证明正确性
证明:算法正确性
void dfs(int h,string s,set &ans){ if(h==s.size()-1){//递归边界 ans.insert(s);//找到一个排序,并且插入 return ; } for(int i=h;i<s.size();i++){//假设原来的元素映射到他的下标,那么我们搜索的是下标的全排序 swap(s[i],s[h]); dfs(h+1,s,ans);//进入下一层递归 swap(s[i],s[h]);//还原s } }
复杂度分析
- 对于第i层循环,设第i层的时间复杂度为f(i),不难发现,对于第二层for循环有,,即循环执行n-i次,因为有这么多个位置允许被互换,内层又执行递归第i+1层
- 不难得出f(size-1)=1,易得,故该算***生成个排序
互异性证明
- 接下来只需要证明,递归搜索生成的序列,具有互异性,即两两不同,则该算法正确
- 反证法
- 假设可以通过不同的互换,得到两个相同的序列,设前面[0,i-1]个位置的互换都相同,在第i层,互换不同,即i和j换,和i和k换,且,
- 又,最后的序列相同,后续的交换无法改变前面的位置,则推出
- 故矛盾,则不存在不同的交换方式,使得两个序列的一样的,证毕
- 注意:此处下标所生成的排序不是字典序递增!
动画演示
代码实现
C++
class Solution { public: void dfs(int h,string s,set &ans){ if(h==s.size()-1){//递归边界 ans.insert(s);//找到一个排序,并且插入 return ; } for(int i=h;i<s.size();i++){//假设原来的元素映射到他的下标,那么我们搜索的是下标的全排序 swap(s[i],s[h]); dfs(h+1,s,ans);//进入下一层递归 swap(s[i],s[h]);//还原s } } vector Permutation(string str) { if(str.empty())return {}; set ans;//因为题目说str中可能有重复元素,所以需要集合来去重,并且起到从小到大排序作用 dfs(0,str,ans);//开始搜索字符串 return vector({ans.begin(),ans.end()});//vector迭代器拷贝初始化,只需要元素的类型一样即可,跟容器无关 } };
python
递归枚举不要s[i]的permutation,然后再拼接上去,递归边界,空串或者一个字符class Solution(object): def Permutation(self, ss): n=len(ss) if n<=1:#递归边界,空串或者一个字符 return ss lst=[] for i in range(n): s1=ss[i] for s2 in self.Permutation(ss[:i]+ss[i+1:]):#递归搜索,不取ss[i] new_str=s1+s2# s1和s2拼接成新的排序 if new_str not in lst :#如果当前没有这个排序 lst.append(new_str) lst=sorted(lst)#字典序,从小到大排序 return lst