剑指offer38:字符串的排列

首先来暴力回溯:

代码如下:

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那里,是个什么意思。。。

然后顺着官方的链接看了一下全排列的东西:

k力扣​​​​​​

然后现在我对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)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值