首先来暴力回溯:
代码如下:
class Solution {
public:
vector<string> permutation(string s) {
vector<string> ans;
string str;
sort(s.begin(),s.end());
vector<int> visit(s.size(),0);
permutationCore(ans,str,s,0,visit);
return ans;
}
void permutationCore(vector<string>& ans,string &str,string &s,int k,vector<int> &visit)
{
if(k==s.size())
{
ans.push_back(str);
return;
}
for(int i=0;i<s.size();++i)
{
if(visit[i] || (i>0&&!visit[i-1]&&s[i-1]==s[i])) continue;
visit[i]=1;
str+=s[i];
permutationCore(ans,str,s,k+1,visit);
// str-=s[i];
str.pop_back();
visit[i]=0;
}
}
};
虽然是暴力,我还是遇到挺多问题的:
1.string莫得-=的重载,不过可以用和vector类似的pop_back实现
2.第一次写的时候没有考虑到字符的重复欸。。。
然后看到官方题解,是这样解答的,首先将字符串中的字符排序,然后让每个递归层选择要选字母的重复序列的最左的空闲字符,emmm不错子,好方法!时间复杂度应该是O(n^2),空间复杂度有点不知道怎么算。。。。。。
结果:
接着看了看《剑指offer》里边有个全排列的交换的做法,也挺厉害的
思路大概是这样:
对每一个字符串的第一个字符,交换和后面的所有字符,然后对后面的字符串进行全排列
代码如下:
class Solution {
public:
vector<string> permutation(string s) {
vector<string> ans;
permutationCore(ans,s,0);
return ans;
}
void permutationCore(vector<string>& ans,string &s,int k)
{
if(k==s.size())
{
for(int i=0;i<ans.size();++i)
{
if(ans[i]==s)
return;
}
ans.push_back(s);
return;
}
for(int i=k;i<s.size();++i)
{
swap(s[k],s[i]);
permutationCore(ans,s,k+1);
swap(s[k],s[i]);
}
}
};
当然,这个超出时间限制了。。。
因为为了检测有无相同的字符串在ans.push_back之前加了个循环检测。。。。。。
不过要是实在想用这个方法,可以把答案的vector换成集合嘿嘿,代码如下所示:
class Solution {
public:
vector<string> permutation(string s) {
unordered_set<string> ans;
permutationCore(ans,s,0);
return vector<string>(ans.begin(),ans.end());
}
void permutationCore(unordered_set<string>& ans,string &s,int k)
{
if(k==s.size())
{
ans.insert(s);
return;
}
for(int i=k;i<s.size();++i)
{
swap(s[k],s[i]);
permutationCore(ans,s,k+1);
swap(s[k],s[i]);
}
}
};
说一下第二种方法的细节:
1.返回时要强转一下
2.insert的接口
啊,然后还有官方题解的第二个解答,思路大概是这样:
自底向上地求取比原字符串大一点的字符串,嗯嗯差不多
直接CV:
class Solution {
public:
bool nextPermutation(string& s) {
int i = s.size() - 2;
while (i >= 0 && s[i] >= s[i + 1]) {
i--;
}
if (i < 0) {
return false;
}
int j = s.size() - 1;
while (j >= 0 && s[i] >= s[j]) {
j--;
}
swap(s[i], s[j]);
reverse(s.begin() + i + 1, s.end());
return true;
}
vector<string> permutation(string s) {
vector<string> ret;
sort(s.begin(), s.end());
do {
ret.push_back(s);
} while (nextPermutation(s));
return ret;
}
};
只能说官方解答牛逼!!不过reverse那里,是个什么意思。。。
然后顺着官方的链接看了一下全排列的东西:
然后现在我对reverse的理解是,它假设现在的字符串是两个部分,一个是从右至左的递增(可以理解为ASCII)的一个序列,然后左边还有一部分并且左边的最右数比右边序列的最左小。
打个比方12354,从右至左的递增序列是右边的54,然后我们要想的是,如何求出下一个刚好之比他大一点(即相邻)的排列呢?如果直接用手去解的话,我们要这样想的对吧:54好大啊,已经是里边两位数能组成的最大值了,那把左边部分的一个最大的数,和右边序列中刚好比他大的一个数交换一下,这样让3变成4,即百位只大了1,是不是刚好大一点呢?
结果是换成12453,发现了么,原来是递增的从右至左的序列,其实交换之后也肯定是一样的性质,即原来的序列从右至左依然是递增的,但是我们要最小的,那得把53换个顺序,变成12435,啊,好像这就是那个我们想要的数了不是么。
当然,上面举的例子有一点特殊性,就是54已经里面能组成的最大的两位数了,其实只要右边满足从右至左的递增序列就好了,你是不是有点疑问,我是有两个:
1.如果右边部分只有一个数怎么办(倒数第二个数就比倒数第一个数小)?只有一个数,它自己就当成一个右边部分好了。
2.如果右边部分不是最大的,例如1247653,还是一样,先变成1257643,再变成1253467,emmm感觉自己也讲的不是很明白,但是核心的思想就一个:找到比当前的排列大一点的下一个排列(听君一席话如听一席话)。。。。。。
算法步骤如下所示(不论右边的序列如何,都是求得比当前排列大一点的下一个相邻排列):
直接cv:
算法过程
标准的“下一个排列”算法可以描述为:
从后向前查找第一个相邻升序的元素对 (i,j),满足 A[i] < A[j]。此时 [j,end) 必然是降序
在 [j,end) 从后向前查找第一个满足 A[i] < A[k] 的 k。A[i]、A[k] 分别就是上文所说的「小数」、「大数」
将 A[i] 与 A[k] 交换
可以断定这时 [j,end) 必然是降序,逆置 [j,end),使其升序
如果在步骤 1 找不到符合的相邻元素对,说明当前 [begin,end) 为一个降序顺序,则直接跳到步骤 4
该方法支持数据重复,且在 C++ STL 中被采用。
作者:imageslr
链接:https://leetcode-cn.com/problems/next-permutation/solution/xia-yi-ge-pai-lie-suan-fa-xiang-jie-si-lu-tui-dao-/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。